blob: c97ac1d9a2cf1f91c8318d5d23cc006dbdf38a0b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.directory.server.core.partition.impl.btree;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import org.apache.directory.server.core.DirectoryServiceConfiguration;
import org.apache.directory.server.core.configuration.PartitionConfiguration;
import org.apache.directory.server.core.enumeration.SearchResultEnumeration;
import org.apache.directory.server.core.interceptor.context.AddOperationContext;
import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
import org.apache.directory.server.core.interceptor.context.OperationContext;
import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
import org.apache.directory.server.core.partition.Oid;
import org.apache.directory.server.core.partition.Partition;
import org.apache.directory.server.core.partition.impl.btree.gui.PartitionViewer;
import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
import org.apache.directory.server.schema.registries.OidRegistry;
import org.apache.directory.server.schema.registries.Registries;
import org.apache.directory.shared.ldap.exception.LdapContextNotEmptyException;
import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
import org.apache.directory.shared.ldap.message.AttributesImpl;
import org.apache.directory.shared.ldap.name.LdapDN;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An abstract {@link Partition} that uses general BTree operations.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public abstract class BTreePartition implements Partition
{
private static final Logger log = LoggerFactory.getLogger( BTreePartition.class );
/** the search engine used to search the database */
private SearchEngine searchEngine = null;
private Optimizer optimizer = new NoOpOptimizer();
protected AttributeTypeRegistry attributeTypeRegistry = null;
protected OidRegistry oidRegistry = null;
// ------------------------------------------------------------------------
// C O N S T R U C T O R S
// ------------------------------------------------------------------------
/**
* Creates a B-tree based context partition.
*/
protected BTreePartition()
{
}
/**
* Allows for schema entity registries to be swapped out during runtime. This is
* primarily here to facilitate the swap out of a temporary bootstrap registry.
* Registry changes require swapping out the search engine used by a partition
* since the registries are used by elements in the search engine.
*
* @param registries the schema entity registries
*/
public void initRegistries( Registries registries )
{
initRegistries1( registries );
}
/**
* This should be called second after initializing the optimizer with
* initOptimizer0. This is the same as calling initRegistries()
* (initRegistries actually calls initRegistries1) except it is protected
* to hide the '1' at the end of the method name. The '1' indicates it
* is the 2nd thing that must be executed during initialization.
*
* @param registries the schema entity registries
*/
protected void initRegistries1( Registries registries )
{
attributeTypeRegistry = registries.getAttributeTypeRegistry();
oidRegistry = registries.getOidRegistry();
ExpressionEvaluator evaluator = new ExpressionEvaluator( this, oidRegistry, attributeTypeRegistry );
ExpressionEnumerator enumerator = new ExpressionEnumerator( this, attributeTypeRegistry, evaluator );
this.searchEngine = new DefaultSearchEngine( this, evaluator, enumerator, optimizer );
}
/**
* Use this method to initialize the indices. Only call this after
* the registries and the optimizer have been enabled. The '2' at the end
* shows this is the 3rd init method called in the init sequence.
*
* @param indices
* @throws NamingException
*/
protected void initIndices2(Set indices ) throws NamingException
{
Set<String> sysOidSet = new HashSet<String>();
sysOidSet.add( Oid.EXISTANCE );
sysOidSet.add( Oid.HIERARCHY );
sysOidSet.add( Oid.UPDN );
sysOidSet.add( Oid.NDN );
sysOidSet.add( Oid.ONEALIAS );
sysOidSet.add( Oid.SUBALIAS );
sysOidSet.add( Oid.ALIAS );
// Used to calculate the system indices we must automatically add
Set<String> customAddedSystemIndices = new HashSet<String>();
for ( Iterator ii = indices.iterator(); ii.hasNext(); /**/ )
{
/*
* NOTE
* ====
*
* The object returned by the indexedAttributes property
* of the configuration may include just a simple set of <String>
* names for the attributes being index OR may include a set
* of IndexConfiguration objects.
*
* If the objects are strings extra information about the
* cacheSize of an index is not available and so the default is
* used. If an IndexConfiguration is available then the custom
* cacheSize is used.
*/
Object nextObject = ii.next();
String name = null;
int cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
int numDupLimit = IndexConfiguration.DEFAULT_DUPLICATE_LIMIT;
// no custom cacheSize info is available so default sticks
if ( nextObject instanceof String )
{
name = ( String ) nextObject;
log.warn( "Using default cache size of {} for index on attribute {}",
new Integer( cacheSize ), name );
}
// custom cache size is used
else if ( nextObject instanceof IndexConfiguration )
{
IndexConfiguration indexConfiguration = ( IndexConfiguration ) nextObject;
name = indexConfiguration.getAttributeId();
cacheSize = indexConfiguration.getCacheSize();
numDupLimit = indexConfiguration.getDuplicateLimit();
if ( cacheSize <= 0 )
{
log.warn( "Cache size {} for index on attribute is null or negative. Using default value.",
new Integer(cacheSize), name );
cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
}
else
{
log.info( "Using cache size of {} for index on attribute {}",
new Integer( cacheSize ), name );
}
if ( cacheSize <= 0 )
{
log.warn( "Duplicate limit {} for index on attribute is null or negative. Using default value.",
new Integer(numDupLimit), name );
cacheSize = IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE;
}
else
{
log.info( "Using duplicate limit of {} for index on attribute {}",
new Integer( numDupLimit ), name );
}
}
String oid = oidRegistry.getOid( name );
AttributeType type = attributeTypeRegistry.lookup( oid );
// check if attribute is a system attribute
if ( sysOidSet.contains( oid ) )
{
if ( oid.equals( Oid.EXISTANCE ) )
{
setExistanceIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.EXISTANCE );
}
else if ( oid.equals( Oid.HIERARCHY ) )
{
setHierarchyIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.HIERARCHY );
}
else if ( oid.equals( Oid.UPDN ) )
{
setUpdnIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.UPDN );
}
else if ( oid.equals( Oid.NDN ) )
{
setNdnIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.NDN );
}
else if ( oid.equals( Oid.ONEALIAS ) )
{
setOneAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.ONEALIAS );
}
else if ( oid.equals( Oid.SUBALIAS ) )
{
setSubAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.SUBALIAS);
}
else if ( oid.equals( Oid.ALIAS ) )
{
setAliasIndexOn( type, cacheSize, numDupLimit );
customAddedSystemIndices.add( Oid.ALIAS );
}
else
{
throw new NamingException( "Unidentified system index " + oid );
}
}
else
{
addIndexOn( type, cacheSize, numDupLimit );
}
}
// -------------------------------------------------------------------
// Add all system indices that were not custom configured by iterating
// through all system index oids and checking of that index is
// contained within the customAddedSystemIndices set. If it is not
// contained in this set then the system index was not custom
// configured above and must be configured with defaults below.
// -------------------------------------------------------------------
for ( Iterator ii = sysOidSet.iterator(); ii.hasNext(); /**/ )
{
String systemIndexName = ( String ) ii.next();
if ( ! customAddedSystemIndices.contains( systemIndexName ) )
{
AttributeType type = attributeTypeRegistry.lookup( systemIndexName );
log.warn( "Using default cache size of {} for index on attribute {}",
new Integer( IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE ), systemIndexName );
if ( systemIndexName.equals( Oid.EXISTANCE ) )
{
setExistanceIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.HIERARCHY ) )
{
setHierarchyIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.UPDN ) )
{
setUpdnIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.NDN ) )
{
setNdnIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.ONEALIAS ) )
{
setOneAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.SUBALIAS ) )
{
setSubAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else if ( systemIndexName.equals( Oid.ALIAS ) )
{
setAliasIndexOn( type, IndexConfiguration.DEFAULT_INDEX_CACHE_SIZE,
IndexConfiguration.DEFAULT_DUPLICATE_LIMIT );
}
else
{
throw new NamingException( "Unidentified system index " + systemIndexName );
}
}
}
}
/**
* Called last (4th) to check if the suffix entry has been created on disk,
* and if not it is created.
*
* @param suffix
* @param entry
* @throws NamingException
*/
protected void initSuffixEntry3( String suffix, Attributes entry ) throws NamingException
{
// add entry for context, if it does not exist
Attributes suffixOnDisk = getSuffixEntry();
if ( suffixOnDisk == null )
{
LdapDN dn = new LdapDN( suffix );
LdapDN normalizedSuffix = LdapDN.normalize( dn, attributeTypeRegistry.getNormalizerMapping() );
add( new AddOperationContext( normalizedSuffix, entry ) );
}
}
/**
* Call this first in the init sequence to initialize the optimizer.
*
* @param cfg
*/
protected void initOptimizer0( PartitionConfiguration cfg )
{
if ( cfg instanceof BTreePartitionConfiguration )
{
if ( ( ( BTreePartitionConfiguration ) cfg ).isOptimizerEnabled() )
{
optimizer = new DefaultOptimizer( this );
}
}
}
public void init( DirectoryServiceConfiguration factoryCfg, PartitionConfiguration cfg )
throws NamingException
{
initOptimizer0( cfg );
initRegistries1( factoryCfg.getRegistries() );
initIndices2( cfg.getIndexedAttributes() );
initSuffixEntry3( cfg.getSuffix(), cfg.getContextEntry() );
}
// ------------------------------------------------------------------------
// Public Accessors - not declared in any interfaces just for this class
// ------------------------------------------------------------------------
/**
* Gets the DefaultSearchEngine used by this ContextPartition to search the
* Database.
*
* @return the search engine
*/
public SearchEngine getSearchEngine()
{
return searchEngine;
}
// ------------------------------------------------------------------------
// ContextPartition Interface Method Implementations
// ------------------------------------------------------------------------
public void delete( OperationContext opContext ) throws NamingException
{
LdapDN dn = opContext.getDn();
Long id = getEntryId( dn.getNormName() );
// don't continue if id is null
if ( id == null )
{
throw new LdapNameNotFoundException( "Could not find entry at '" + dn + "' to delete it!" );
}
if ( getChildCount( id ) > 0 )
{
LdapContextNotEmptyException cnee = new LdapContextNotEmptyException( "[66] Cannot delete entry " + dn
+ " it has children!" );
cnee.setRemainingName( dn );
throw cnee;
}
delete( id );
}
public abstract void add( OperationContext opContext ) throws NamingException;
public abstract void modify( OperationContext opContext ) throws NamingException;
private static final String[] ENTRY_DELETED_ATTRS = new String[] { "entrydeleted" };
public NamingEnumeration list( OperationContext opContext ) throws NamingException
{
SearchResultEnumeration list;
list = new BTreeSearchResultEnumeration( ENTRY_DELETED_ATTRS, list( getEntryId( opContext.getDn().getNormName() ) ),
this, attributeTypeRegistry );
return list;
}
public NamingEnumeration<SearchResult> search( OperationContext opContext )
throws NamingException
{
SearchControls searchCtls = ((SearchOperationContext)opContext).getSearchControls();
String[] attrIds = searchCtls.getReturningAttributes();
NamingEnumeration underlying = null;
underlying = searchEngine.search(
opContext.getDn(),
((SearchOperationContext)opContext).getEnv(),
((SearchOperationContext)opContext).getFilter(),
searchCtls );
return new BTreeSearchResultEnumeration( attrIds, underlying, this, attributeTypeRegistry );
}
public Attributes lookup( OperationContext opContext ) throws NamingException
{
LookupOperationContext ctx = (LookupOperationContext)opContext;
Attributes entry = lookup( getEntryId( ctx.getDn().getNormName() ) );
if ( ( ctx.getAttrsId() == null ) || ( ctx.getAttrsId().size() == 0 ) )
{
return entry;
}
Attributes retval = new AttributesImpl();
for ( String attrId:ctx.getAttrsId() )
{
Attribute attr = entry.get( attrId );
if ( attr != null )
{
retval.put( attr );
}
}
return retval;
}
public boolean hasEntry( OperationContext opContext ) throws NamingException
{
return null != getEntryId( opContext.getDn().getNormName() );
}
public abstract void rename( OperationContext opContext ) throws NamingException;
public abstract void move( OperationContext opContext ) throws NamingException;
public abstract void moveAndRename( OperationContext opContext )
throws NamingException;
public abstract void sync() throws NamingException;
public abstract void destroy();
public abstract boolean isInitialized();
public void inspect() throws Exception
{
PartitionViewer viewer = new PartitionViewer( this, searchEngine );
viewer.execute();
}
////////////////////
// public abstract methods
// ------------------------------------------------------------------------
// Index Operations
// ------------------------------------------------------------------------
public abstract void addIndexOn( AttributeType attribute, int cacheSize, int numDupLimit ) throws NamingException;
public abstract boolean hasUserIndexOn( String attribute ) throws NamingException;
public abstract boolean hasSystemIndexOn( String attribute ) throws NamingException;
public abstract Index getExistanceIndex();
/**
* Gets the Index mapping the BigInteger primary keys of parents to the
* BigInteger primary keys of their children.
*
* @return the hierarchy Index
*/
public abstract Index getHierarchyIndex();
/**
* Gets the Index mapping user provided distinguished names of entries as
* Strings to the BigInteger primary keys of entries.
*
* @return the user provided distinguished name Index
*/
public abstract Index getUpdnIndex();
/**
* Gets the Index mapping the normalized distinguished names of entries as
* Strings to the BigInteger primary keys of entries.
*
* @return the normalized distinguished name Index
*/
public abstract Index getNdnIndex();
/**
* Gets the alias index mapping parent entries with scope expanding aliases
* children one level below them; this system index is used to dereference
* aliases on one/single level scoped searches.
*
* @return the one alias index
*/
public abstract Index getOneAliasIndex();
/**
* Gets the alias index mapping relative entries with scope expanding
* alias descendents; this system index is used to dereference aliases on
* subtree scoped searches.
*
* @return the sub alias index
*/
public abstract Index getSubAliasIndex();
/**
* Gets the system index defined on the ALIAS_ATTRIBUTE which for LDAP would
* be the aliasedObjectName and for X.500 would be aliasedEntryName.
*
* @return the index on the ALIAS_ATTRIBUTE
*/
public abstract Index getAliasIndex();
/**
* Sets the system index defined on the ALIAS_ATTRIBUTE which for LDAP would
* be the aliasedObjectName and for X.500 would be aliasedEntryName.
*
* @param attrType the index on the ALIAS_ATTRIBUTE
*/
public abstract void setAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the attribute existance Index.
*
* @param attrType the attribute existance Index
*/
public abstract void setExistanceIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the hierarchy Index.
*
* @param attrType the hierarchy Index
*/
public abstract void setHierarchyIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the user provided distinguished name Index.
*
* @param attrType the updn Index
*/
public abstract void setUpdnIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the normalized distinguished name Index.
*
* @param attrType the ndn Index
*/
public abstract void setNdnIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the alias index mapping parent entries with scope expanding aliases
* children one level below them; this system index is used to dereference
* aliases on one/single level scoped searches.
*
* @param attrType a one level alias index
*/
public abstract void setOneAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
/**
* Sets the alias index mapping relative entries with scope expanding
* alias descendents; this system index is used to dereference aliases on
* subtree scoped searches.
*
* @param attrType a subtree alias index
*/
public abstract void setSubAliasIndexOn( AttributeType attrType, int cacheSize, int numDupLimit ) throws NamingException;
public abstract Index getUserIndex( String attribute ) throws IndexNotFoundException;
public abstract Index getSystemIndex( String attribute ) throws IndexNotFoundException;
public abstract Long getEntryId( String dn ) throws NamingException;
public abstract String getEntryDn( Long id ) throws NamingException;
public abstract Long getParentId( String dn ) throws NamingException;
public abstract Long getParentId( Long childId ) throws NamingException;
/**
* Gets the user provided distinguished name.
*
* @param id the entry id
* @return the user provided distinguished name
* @throws NamingException if the updn index cannot be accessed
*/
public abstract String getEntryUpdn( Long id ) throws NamingException;
/**
* Gets the user provided distinguished name.
*
* @param dn the normalized distinguished name
* @return the user provided distinguished name
* @throws NamingException if the updn and ndn indices cannot be accessed
*/
public abstract String getEntryUpdn( String dn ) throws NamingException;
public abstract Attributes lookup( Long id ) throws NamingException;
public abstract void delete( Long id ) throws NamingException;
public abstract NamingEnumeration list( Long id ) throws NamingException;
public abstract int getChildCount( Long id ) throws NamingException;
public abstract Attributes getSuffixEntry() throws NamingException;
public abstract void setProperty( String key, String value ) throws NamingException;
public abstract String getProperty( String key ) throws NamingException;
public abstract Iterator getUserIndices();
public abstract Iterator getSystemIndices();
public abstract Attributes getIndices( Long id ) throws NamingException;
/**
* Gets the count of the total number of entries in the database.
*
* TODO shouldn't this be a BigInteger instead of an int?
*
* @return the number of entries in the database
* @throws NamingException if there is a failure to read the count
*/
public abstract int count() throws NamingException;
}