blob: b5be49c92c6a7a505cf06934292e650c1ea0fe8d [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.factory;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.api.util.Network;
import org.apache.directory.api.util.Strings;
import org.apache.directory.server.core.annotations.AnnotationUtils;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.ApplyLdifs;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateAuthenticator;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.annotations.LoadSchema;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.DnFactory;
import org.apache.directory.server.core.api.interceptor.Interceptor;
import org.apache.directory.server.core.api.partition.Partition;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.authn.Authenticator;
import org.apache.directory.server.core.authn.DelegatingAuthenticator;
import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
import org.apache.directory.server.core.partition.impl.btree.mavibot.MavibotIndex;
import org.apache.directory.server.i18n.I18n;
import org.junit.runner.Description;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Helper class used to create a DS from the annotations
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public final class DSAnnotationProcessor
{
/** A logger for this class */
private static final Logger LOG = LoggerFactory.getLogger( DSAnnotationProcessor.class );
private DSAnnotationProcessor()
{
}
/**
* Create the DirectoryService
*
* @param dsBuilder The DirectoryService builder
* @return an instance of DirectoryService
* @throws Exception If the DirectoryService cannot be created
*/
public static DirectoryService createDS( CreateDS dsBuilder )
throws Exception
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Starting DS {}...", dsBuilder.name() );
}
Class<?> factory = dsBuilder.factory();
DirectoryServiceFactory dsf = ( DirectoryServiceFactory ) factory.newInstance();
DirectoryService service = dsf.getDirectoryService();
service.setAccessControlEnabled( dsBuilder.enableAccessControl() );
service.setAllowAnonymousAccess( dsBuilder.allowAnonAccess() );
service.getChangeLog().setEnabled( dsBuilder.enableChangeLog() );
dsf.init( dsBuilder.name() );
for ( Class<?> interceptorClass : dsBuilder.additionalInterceptors() )
{
service.addLast( ( Interceptor ) interceptorClass.newInstance() );
}
List<Interceptor> interceptorList = service.getInterceptors();
if ( dsBuilder.authenticators().length != 0 )
{
AuthenticationInterceptor authenticationInterceptor = null;
for ( Interceptor interceptor : interceptorList )
{
if ( interceptor instanceof AuthenticationInterceptor )
{
authenticationInterceptor = ( AuthenticationInterceptor ) interceptor;
break;
}
}
if ( authenticationInterceptor == null )
{
throw new IllegalStateException(
"authentication interceptor not found" );
}
Set<Authenticator> authenticators = new HashSet<>();
for ( CreateAuthenticator createAuthenticator : dsBuilder
.authenticators() )
{
Authenticator auth = createAuthenticator.type().newInstance();
if ( auth instanceof DelegatingAuthenticator )
{
DelegatingAuthenticator dauth = ( DelegatingAuthenticator ) auth;
String host = createAuthenticator.delegateHost();
if ( Strings.isEmpty( host ) )
{
host = Network.LOOPBACK_HOSTNAME;
}
dauth.setDelegateHost( host );
dauth.setDelegatePort( createAuthenticator.delegatePort() );
dauth.setDelegateSsl( createAuthenticator.delegateSsl() );
dauth.setDelegateTls( createAuthenticator.delegateTls() );
dauth.setBaseDn( service.getDnFactory().create( createAuthenticator.baseDn() ) );
dauth.setDelegateSslTrustManagerFQCN( createAuthenticator.delegateSslTrustManagerFQCN() );
dauth.setDelegateTlsTrustManagerFQCN( createAuthenticator.delegateTlsTrustManagerFQCN() );
}
authenticators.add( auth );
}
authenticationInterceptor.setAuthenticators( authenticators );
authenticationInterceptor.init( service );
}
service.setInterceptors( interceptorList );
SchemaManager schemaManager = service.getSchemaManager();
// process the schemas
for ( LoadSchema loadedSchema : dsBuilder.loadedSchemas() )
{
String schemaName = loadedSchema.name();
Boolean enabled = loadedSchema.enabled();
// Check if the schema is loaded or not
boolean isLoaded = schemaManager.isSchemaLoaded( schemaName );
if ( !isLoaded )
{
// We have to load the schema, if it exists
try
{
isLoaded = schemaManager.load( schemaName );
}
catch ( LdapUnwillingToPerformException lutpe )
{
// Cannot load the schema, it does not exists
LOG.error( lutpe.getMessage() );
continue;
}
}
if ( isLoaded )
{
if ( enabled )
{
schemaManager.enable( schemaName );
if ( schemaManager.isDisabled( schemaName ) )
{
LOG.error( "Cannot enable {}", schemaName );
}
}
else
{
schemaManager.disable( schemaName );
if ( schemaManager.isEnabled( schemaName ) )
{
LOG.error( "Cannot disable {}", schemaName );
}
}
}
LOG.debug( "Loading schema {}, enabled= {}", schemaName, enabled );
}
// Process the Partition, if any.
for ( CreatePartition createPartition : dsBuilder.partitions() )
{
Partition partition;
// Determine the partition type
if ( createPartition.type() == Partition.class )
{
// The annotation does not specify a specific partition type.
// We use the partition factory to create partition and index
// instances.
PartitionFactory partitionFactory = dsf.getPartitionFactory();
partition = partitionFactory.createPartition(
schemaManager,
service.getDnFactory(),
createPartition.name(),
createPartition.suffix(),
createPartition.cacheSize(),
new File( service.getInstanceLayout().getPartitionsDirectory(), createPartition.name() ) );
CreateIndex[] indexes = createPartition.indexes();
for ( CreateIndex createIndex : indexes )
{
partitionFactory.addIndex( partition,
createIndex.attribute(), createIndex.cacheSize() );
}
partition.initialize();
}
else
{
// The annotation contains a specific partition type, we use
// that type.
Class<?>[] partypes = new Class[]
{ SchemaManager.class, DnFactory.class };
Constructor<?> constructor = createPartition.type().getConstructor( partypes );
partition = ( Partition ) constructor.newInstance( schemaManager, service.getDnFactory() );
partition.setId( createPartition.name() );
partition.setSuffixDn( new Dn( schemaManager, createPartition.suffix() ) );
if ( partition instanceof AbstractBTreePartition )
{
AbstractBTreePartition btreePartition = ( AbstractBTreePartition ) partition;
btreePartition.setCacheSize( createPartition.cacheSize() );
btreePartition.setPartitionPath( new File( service
.getInstanceLayout().getPartitionsDirectory(),
createPartition.name() ).toURI() );
// Process the indexes if any
CreateIndex[] indexes = createPartition.indexes();
for ( CreateIndex createIndex : indexes )
{
if ( createIndex.type() == MavibotIndex.class )
{
// Mavibot index
MavibotIndex index = new MavibotIndex( createIndex.attribute(), false );
btreePartition.addIndexedAttributes( index );
}
else
{
// The annotation does not specify a specific index
// type.
// We use the generic index implementation.
JdbmIndex index = new JdbmIndex( createIndex.attribute(), false );
btreePartition.addIndexedAttributes( index );
}
}
}
}
partition.setSchemaManager( schemaManager );
// Inject the partition into the DirectoryService
service.addPartition( partition );
// Last, process the context entry
ContextEntry contextEntry = createPartition.contextEntry();
if ( contextEntry != null )
{
injectEntries( service, contextEntry.entryLdif() );
}
}
return service;
}
/**
* Create a DirectoryService from a Unit test annotation
*
* @param description The annotations containing the info from which we will create
* the DS
* @return A valid DirectoryService
* @throws Exception If the DirectoryService instance can't be returned
*/
public static DirectoryService getDirectoryService( Description description )
throws Exception
{
CreateDS dsBuilder = description.getAnnotation( CreateDS.class );
if ( dsBuilder != null )
{
return createDS( dsBuilder );
}
else
{
LOG.debug( "No {} DS.", description.getDisplayName() );
return null;
}
}
/**
* Create a DirectoryService from an annotation. The @CreateDS annotation
* must be associated with either the method or the encapsulating class. We
* will first try to get the annotation from the method, and if there is
* none, then we try at the class level.
*
* @return A valid DS
* @throws Exception If the DirectoryService instance can't be returned
*/
public static DirectoryService getDirectoryService() throws Exception
{
Object instance = AnnotationUtils.getInstance( CreateDS.class );
CreateDS dsBuilder = null;
if ( instance != null )
{
dsBuilder = ( CreateDS ) instance;
// Ok, we have found a CreateDS annotation. Process it now.
return createDS( dsBuilder );
}
throw new LdapException( I18n.err( I18n.ERR_114 ) );
}
/**
* injects an LDIF entry in the given DirectoryService
*
* @param entry the LdifEntry to be injected
* @param service the DirectoryService
* @throws Exception If the entry cannot be injected
*/
private static void injectEntry( LdifEntry entry, DirectoryService service )
throws LdapException
{
if ( entry.isChangeAdd() || entry.isLdifContent() )
{
service.getAdminSession().add(
new DefaultEntry( service.getSchemaManager(), entry
.getEntry() ) );
}
else if ( entry.isChangeModify() )
{
service.getAdminSession().modify( entry.getDn(),
entry.getModifications() );
}
else
{
String message = I18n.err( I18n.ERR_117, entry.getChangeType() );
throw new LdapException( message );
}
}
/**
* injects the LDIF entries present in a LDIF file
*
* @param clazz The class which classLoaded will be use to retrieve the resources
* @param service the DirectoryService
* @param ldifFiles array of LDIF file names (only )
* @throws Exception If we weren't able to inject LdifFiles
*/
public static void injectLdifFiles( Class<?> clazz,
DirectoryService service, String[] ldifFiles ) throws Exception
{
if ( ( ldifFiles != null ) && ( ldifFiles.length > 0 ) )
{
for ( String ldifFile : ldifFiles )
{
InputStream is = clazz.getClassLoader().getResourceAsStream(
ldifFile );
if ( is == null )
{
throw new FileNotFoundException( "LDIF file '" + ldifFile
+ "' not found." );
}
else
{
LdifReader ldifReader = new LdifReader( is );
for ( LdifEntry entry : ldifReader )
{
injectEntry( entry, service );
}
ldifReader.close();
}
}
}
}
/**
* Inject an ldif String into the server. Dn must be relative to the root.
*
* @param service the directory service to use
* @param ldif the ldif containing entries to add to the server.
* @throws Exception if there is a problem adding the entries from the LDIF
*/
public static void injectEntries( DirectoryService service, String ldif )
throws Exception
{
try ( LdifReader reader = new LdifReader() )
{
List<LdifEntry> entries = reader.parseLdif( ldif );
for ( LdifEntry entry : entries )
{
injectEntry( entry, service );
}
}
}
/**
* Load the schemas, and enable/disable them.
*
* @param desc The description
* @param service The DirectoryService instance
*/
public static void loadSchemas( Description desc, DirectoryService service )
{
if ( desc == null )
{
return;
}
LoadSchema loadSchema = desc
.getAnnotation( LoadSchema.class );
if ( loadSchema != null )
{
System.out.println( loadSchema );
}
}
private static boolean isDn( String str )
{
if ( ( Strings.isEmpty( str ) ) | ( str.length() < 3 ) )
{
return false;
}
return ( ( ( str.charAt( 0 ) == 'd' ) || ( str.charAt( 0 ) == 'D' ) )
&& ( ( str.charAt( 1 ) == 'n' ) || ( str.charAt( 1 ) == 'N' ) )
&& ( str.charAt( 2 ) == ':' ) );
}
/**
* Apply the LDIF entries to the given service
*
* @param desc The description
* @param service The DirectoryService instance
* @throws Exception If we can't apply the ldifs
*/
public static void applyLdifs( Description desc, DirectoryService service )
throws Exception
{
if ( desc == null )
{
return;
}
ApplyLdifFiles applyLdifFiles = desc
.getAnnotation( ApplyLdifFiles.class );
if ( applyLdifFiles != null )
{
LOG.debug( "Applying {} to {}", applyLdifFiles.value(),
desc.getDisplayName() );
injectLdifFiles( applyLdifFiles.clazz(), service, applyLdifFiles.value() );
}
ApplyLdifs applyLdifs = desc.getAnnotation( ApplyLdifs.class );
if ( ( applyLdifs != null ) && ( applyLdifs.value() != null ) )
{
String[] ldifs = applyLdifs.value();
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < ldifs.length; )
{
String str = ldifs[i++].trim();
if ( isDn( str ) )
{
sb.append( str ).append( '\n' );
// read the rest of lines till we encounter Dn again
while ( i < ldifs.length )
{
str = ldifs[i++];
if ( !isDn( str ) )
{
sb.append( str ).append( '\n' );
}
else
{
break;
}
}
LOG.debug( "Applying {} to {}", sb, desc.getDisplayName() );
injectEntries( service, sb.toString() );
sb.setLength( 0 );
i--; // step up a line
}
}
}
}
}