blob: 8aaa55b832d7376017cd253ff1043426f291505f [file] [log] [blame]
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.ldap.server.partition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.naming.ConfigurationException;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import org.apache.ldap.common.MultiException;
import org.apache.ldap.common.NotImplementedException;
import org.apache.ldap.common.exception.LdapNameNotFoundException;
import org.apache.ldap.common.filter.ExprNode;
import org.apache.ldap.common.filter.PresenceNode;
import org.apache.ldap.common.message.LockableAttributeImpl;
import org.apache.ldap.common.message.LockableAttributes;
import org.apache.ldap.common.message.LockableAttributesImpl;
import org.apache.ldap.common.name.LdapName;
import org.apache.ldap.common.util.DateUtils;
import org.apache.ldap.common.util.NamespaceTools;
import org.apache.ldap.common.util.SingletonEnumeration;
import org.apache.ldap.server.configuration.ContextPartitionConfiguration;
import org.apache.ldap.server.configuration.MutableContextPartitionConfiguration;
import org.apache.ldap.server.jndi.ContextFactoryConfiguration;
import org.apache.ldap.server.partition.impl.btree.jdbm.JdbmContextPartition;
/**
* A nexus for partitions dedicated for storing entries specific to a naming
* context.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class DefaultContextPartitionNexus extends ContextPartitionNexus
{
/** the vendorName string proudly set to: Apache Software Foundation*/
private static final String ASF = "Apache Software Foundation";
/** the vendorName DSE operational attribute */
private static final String VENDORNAME_ATTR = "vendorName";
/** the namingContexts DSE operational attribute */
private static final String NAMINGCTXS_ATTR = "namingContexts";
/** the closed state of this partition */
private boolean initialized;
/** the system backend */
private ContextPartition system;
/** the backends keyed by normalized suffix strings */
private HashMap partitions = new HashMap();
/** the read only rootDSE attributes */
private final Attributes rootDSE;
/**
* Creates the root nexus singleton of the entire system. The root DSE has
* several attributes that are injected into it besides those that may
* already exist. As partitions are added to the system more namingContexts
* attributes are added to the rootDSE.
*
* @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
*/
public DefaultContextPartitionNexus( Attributes rootDSE )
{
// setup that root DSE
this.rootDSE = rootDSE;
Attribute attr = new LockableAttributeImpl( "subschemaSubentry" );
attr.add( "cn=schema,ou=system" );
rootDSE.put( attr );
attr = new LockableAttributeImpl( "supportedLDAPVersion" );
rootDSE.put( attr );
attr.add( "3" );
attr = new LockableAttributeImpl( "objectClass" );
rootDSE.put( attr );
attr.add( "top" );
attr.add( "extensibleObject" );
attr = new LockableAttributeImpl( NAMINGCTXS_ATTR );
rootDSE.put( attr );
attr = new LockableAttributeImpl( VENDORNAME_ATTR );
attr.add( ASF );
rootDSE.put( attr );
}
public void init( ContextFactoryConfiguration factoryCfg, ContextPartitionConfiguration cfg ) throws NamingException
{
// NOTE: We ignore ContextPartitionConfiguration parameter here.
if( initialized )
{
return;
}
List initializedPartitions = new ArrayList();
initializeSystemPartition( factoryCfg );
initializedPartitions.add( system );
Iterator i = factoryCfg.getStartupConfiguration().getContextPartitionConfigurations().iterator();
try
{
while( i.hasNext() )
{
cfg = ( ContextPartitionConfiguration ) i.next();
ContextPartition partition = cfg.getContextPartition();
// Turn on default indices
MutableContextPartitionConfiguration mcfg =
new MutableContextPartitionConfiguration();
mcfg.setName( cfg.getName() );
mcfg.setSuffix( cfg.getSuffix() );
mcfg.setContextEntry( cfg.getContextEntry() );
mcfg.setContextPartition( partition );
Set indexedAttrs = cfg.getIndexedAttributes();
indexedAttrs.add( Oid.ALIAS );
indexedAttrs.add( Oid.EXISTANCE );
indexedAttrs.add( Oid.HIERARCHY );
indexedAttrs.add( Oid.NDN );
indexedAttrs.add( Oid.ONEALIAS );
indexedAttrs.add( Oid.SUBALIAS );
indexedAttrs.add( Oid.UPDN );
mcfg.setIndexedAttributes( indexedAttrs );
partition.init( factoryCfg, mcfg );
initializedPartitions.add( 0, partition );
register( partition );
}
initialized = true;
}
finally
{
if( !initialized )
{
i = initializedPartitions.iterator();
while( i.hasNext() )
{
ContextPartition partition = ( ContextPartition ) i.next();
i.remove();
try
{
partition.destroy();
}
catch( Exception e )
{
e.printStackTrace();
}
finally
{
unregister( partition );
}
}
}
}
}
private void initializeSystemPartition( ContextFactoryConfiguration factoryCfg ) throws NamingException
{
// initialize system partition first
MutableContextPartitionConfiguration systemCfg = new MutableContextPartitionConfiguration();
system = new JdbmContextPartition(); // using default implementation.
systemCfg.setName( "system" );
systemCfg.setSuffix( ContextPartitionNexus.SYSTEM_PARTITION_SUFFIX );
systemCfg.setContextPartition( system );
// Add indexed attributes for system partition
Set indexedSystemAttrs = new HashSet();
indexedSystemAttrs.add( Oid.ALIAS );
indexedSystemAttrs.add( Oid.EXISTANCE );
indexedSystemAttrs.add( Oid.HIERARCHY );
indexedSystemAttrs.add( Oid.NDN );
indexedSystemAttrs.add( Oid.ONEALIAS );
indexedSystemAttrs.add( Oid.SUBALIAS );
indexedSystemAttrs.add( Oid.UPDN );
systemCfg.setIndexedAttributes( indexedSystemAttrs );
// Add context entry for system partition
Attributes systemEntry = new BasicAttributes();
Attribute objectClassAttr = new BasicAttribute( "objectClass" );
objectClassAttr.add( "top" );
objectClassAttr.add( "organizationalUnit" );
systemEntry.put( objectClassAttr );
systemEntry.put( "creatorsName", ContextPartitionNexus.ADMIN_PRINCIPAL ) ;
systemEntry.put( "createTimestamp", DateUtils.getGeneralizedTime() ) ;
systemEntry.put(
NamespaceTools.getRdnAttribute( ContextPartitionNexus.SYSTEM_PARTITION_SUFFIX ),
NamespaceTools.getRdnValue( ContextPartitionNexus.SYSTEM_PARTITION_SUFFIX ) ) ;
systemCfg.setContextEntry( systemEntry );
system.init( factoryCfg, systemCfg );
register( system );
}
public boolean isInitialized()
{
return initialized;
}
public synchronized void destroy()
{
if ( !initialized )
{
return;
}
MultiException error = null;
Iterator list = this.partitions.values().iterator();
// make sure this loop is not fail fast so all backing stores can
// have an attempt at closing down and synching their cached entries
while ( list.hasNext() )
{
ContextPartition partition = ( ContextPartition ) list.next();
try
{
partition.sync();
partition.destroy();
}
catch ( NamingException e )
{
e.printStackTrace();
if ( error == null )
{
error = new MultiException( "Grouping many exceptions on root nexus close()" );
}
// @todo really need to send this info to a monitor
error.addThrowable( e );
}
}
initialized = false;
if ( error != null )
{
String msg = "Encountered failures while performing a close() operation on backing stores";
NamingException total = new NamingException( msg );
total.setRootCause( error );
total.printStackTrace();
}
}
/**
* @see ContextPartition#sync()
*/
public void sync() throws NamingException
{
MultiException error = null;
Iterator list = this.partitions.values().iterator();
while ( list.hasNext() )
{
ContextPartition store = ( ContextPartition ) list.next();
try
{
store.sync();
}
catch ( NamingException e )
{
e.printStackTrace();
if ( error == null )
{
error = new MultiException( "Grouping many exceptions on root nexus sync()" );
}
// @todo really need to send this info to a monitor
error.addThrowable( e );
}
}
if ( error != null )
{
String msg = "Encountered failures while performing a sync() operation on backing stores";
NamingException total = new NamingException( msg );
total.setRootCause( error );
}
}
// ------------------------------------------------------------------------
// BackendNexus Interface Method Implementations
// ------------------------------------------------------------------------
public ContextPartition getSystemPartition()
{
return system;
}
/**
* @see ContextPartitionNexus#getLdapContext()
*/
public LdapContext getLdapContext()
{
throw new NotImplementedException();
}
/**
* @see ContextPartitionNexus#getMatchedName(javax.naming.Name, boolean)
*/
public Name getMatchedName( Name dn, boolean normalized ) throws NamingException
{
dn = ( Name ) dn.clone();
while ( dn.size() > 0 )
{
if ( hasEntry( dn ) )
{
return dn;
}
dn = dn.getSuffix( 1 );
}
return dn;
}
public Name getSuffix( boolean normalized )
{
return new LdapName();
}
/**
* @see org.apache.ldap.server.partition.ContextPartitionNexus#getSuffix(javax.naming.Name, boolean)
*/
public Name getSuffix( Name dn, boolean normalized ) throws NamingException
{
ContextPartition backend = getBackend( dn );
return backend.getSuffix( normalized );
}
/**
* @see org.apache.ldap.server.partition.ContextPartitionNexus#listSuffixes(boolean)
*/
public Iterator listSuffixes( boolean normalized ) throws NamingException
{
return Collections.unmodifiableSet( partitions.keySet() ).iterator();
}
public Attributes getRootDSE()
{
return rootDSE;
}
/**
* Registers an ContextPartition with this BackendManager. Called by each
* ContextPartition implementation after it has started to register for
* backend operation calls. This method effectively puts the
* ContextPartition's naming context online.
*
* Operations against the naming context should result in an LDAP BUSY
* result code in the returnValue if the naming context is not online.
*
* @param partition ContextPartition component to register with this
* BackendNexus.
* @throws ConfigurationException
*/
private void register( ContextPartition partition ) throws NamingException
{
String key = partition.getSuffix( true ).toString();
if( partitions.containsKey( key ) )
{
throw new ConfigurationException( "Duplicate partition suffix: " + key );
}
partitions.put( key, partition );
Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
namingContexts.add( partition.getSuffix( false ).toString() );
}
/**
* Unregisters an ContextPartition with this BackendManager. Called for each
* registered Backend right befor it is to be stopped. This prevents
* protocol server requests from reaching the Backend and effectively puts
* the ContextPartition's naming context offline.
*
* Operations against the naming context should result in an LDAP BUSY
* result code in the returnValue if the naming context is not online.
*
* @param partition ContextPartition component to unregister with this
* BackendNexus.
*/
private void unregister( ContextPartition partition ) throws NamingException
{
Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
namingContexts.remove( partition.getSuffix( false ).toString() );
partitions.remove( partition.getSuffix( true ).toString() );
}
// ------------------------------------------------------------------------
// Backend Interface Method Implementations
// ------------------------------------------------------------------------
/**
* @see ContextPartition#delete(javax.naming.Name)
*/
public void delete( Name dn ) throws NamingException
{
ContextPartition backend = getBackend( dn );
backend.delete( dn );
}
/**
* Looks up the backend corresponding to the entry first, then checks to
* see if the entry already exists. If so an exception is thrown. If not
* the add operation against the backend proceeds. This check is performed
* here so backend implementors do not have to worry about performing these
* kinds of checks.
*
* @see org.apache.ldap.server.partition.ContextPartition#add(String, Name, Attributes)
*/
public void add( String updn, Name dn, Attributes an_entry ) throws NamingException
{
ContextPartition backend = getBackend( dn );
backend.add( updn, dn, an_entry );
}
/**
* @see ContextPartition#modify(Name, int,Attributes)
*/
public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
{
ContextPartition backend = getBackend( dn );
backend.modify( dn, modOp, mods );
}
/**
* @see ContextPartition#modify(javax.naming.Name,
* javax.naming.directory.ModificationItem[])
*/
public void modify( Name dn, ModificationItem[] mods ) throws NamingException
{
ContextPartition backend = getBackend( dn );
backend.modify( dn, mods );
}
/**
* @see ContextPartition#list(javax.naming.Name)
*/
public NamingEnumeration list( Name base ) throws NamingException
{
ContextPartition backend = getBackend( base );
return backend.list( base );
}
/**
* @see ContextPartition#search(Name, Map, ExprNode, SearchControls)
*/
public NamingEnumeration search( Name base, Map env, ExprNode filter, SearchControls searchCtls )
throws NamingException
{
if ( base.size() == 0 )
{
boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
boolean isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equalsIgnoreCase( "objectclass" );
/*
* if basedn is "", filter is "(objectclass=*)" and scope is object
* then we have a request for the rootDSE
*/
if ( filter instanceof PresenceNode && isObjectScope && isSearchAll )
{
Attributes attrs = ( Attributes ) getRootDSE().clone();
String[] ids = searchCtls.getReturningAttributes();
if ( ids != null && ids.length > 0 )
{
boolean doSwap = true;
Attributes askedFor = new LockableAttributesImpl();
for ( int ii = 0; ii < ids.length; ii++ )
{
if ( ids[ii].trim().equals( "*" ) )
{
doSwap = false;
break;
}
if ( attrs.get( ids[ii] ) != null )
{
askedFor.put( attrs.get( ids[ii] ) );
}
}
if ( doSwap )
{
attrs = askedFor;
}
}
SearchResult result = new SearchResult( "", null, attrs, false );
return new SingletonEnumeration( result );
}
throw new LdapNameNotFoundException();
}
ContextPartition backend = getBackend( base );
return backend.search( base, env, filter, searchCtls );
}
/**
* @see ContextPartition#lookup(javax.naming.Name)
*/
public Attributes lookup( Name dn ) throws NamingException
{
if ( dn.size() == 0 )
{
LockableAttributes retval = ( LockableAttributes ) rootDSE.clone();
retval.setLocked( true );
return retval;
}
ContextPartition backend = getBackend( dn );
return backend.lookup( dn );
}
/**
* @see org.apache.ldap.server.partition.ContextPartition#lookup(javax.naming.Name, String[])
*/
public Attributes lookup( Name dn, String[] attrIds ) throws NamingException
{
if ( dn.size() == 0 )
{
LockableAttributes retval = new LockableAttributesImpl();
NamingEnumeration list = rootDSE.getIDs();
while ( list.hasMore() )
{
String id = ( String ) list.next();
Attribute attr = rootDSE.get( id );
retval.put( ( Attribute ) attr.clone() );
}
retval.setLocked( true );
return retval;
}
ContextPartition backend = getBackend( dn );
return backend.lookup( dn, attrIds );
}
/**
* @see ContextPartition#hasEntry(javax.naming.Name)
*/
public boolean hasEntry( Name dn ) throws NamingException
{
if ( dn.size() == 0 )
{
return true;
}
ContextPartition backend = getBackend( dn );
return backend.hasEntry( dn );
}
/**
* @see ContextPartition#isSuffix(javax.naming.Name)
*/
public boolean isSuffix( Name dn )
{
return partitions.containsKey( dn.toString() );
}
/**
* @see ContextPartition#modifyRn(Name, String, boolean)
*/
public void modifyRn( Name dn, String newRdn, boolean deleteOldRdn ) throws NamingException
{
ContextPartition backend = getBackend( dn );
backend.modifyRn( dn, newRdn, deleteOldRdn );
}
/**
* @see ContextPartition#move(Name, Name)
*/
public void move( Name oriChildName, Name newParentName ) throws NamingException
{
ContextPartition backend = getBackend( oriChildName );
backend.move( oriChildName, newParentName );
}
/**
* @see ContextPartition#move(javax.naming.Name,
* javax.naming.Name, java.lang.String, boolean)
*/
public void move( Name oldChildDn, Name newParentDn, String newRdn,
boolean deleteOldRdn ) throws NamingException
{
ContextPartition backend = getBackend( oldChildDn );
backend.move( oldChildDn, newParentDn, newRdn, deleteOldRdn );
}
// ------------------------------------------------------------------------
// Private Methods
// ------------------------------------------------------------------------
/**
* Gets the backend partition associated with a normalized dn.
*
* @param dn the normalized distinguished name to resolve to a backend
* @return the backend partition associated with the normalized dn
* @throws NamingException if the name cannot be resolved to a backend
*/
private ContextPartition getBackend( Name dn ) throws NamingException
{
Name clonedDn = ( Name ) dn.clone();
while ( clonedDn.size() > 0 )
{
if ( partitions.containsKey( clonedDn.toString() ) )
{
return ( ContextPartition ) partitions.get( clonedDn.toString() );
}
clonedDn.remove( clonedDn.size() - 1 );
}
throw new NameNotFoundException( dn.toString() );
}
}