View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.server.core.factory;
20  
21  
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.InputStream;
25  import java.lang.reflect.Constructor;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
31  import org.apache.directory.api.ldap.model.exception.LdapException;
32  import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException;
33  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
34  import org.apache.directory.api.ldap.model.ldif.LdifReader;
35  import org.apache.directory.api.ldap.model.name.Dn;
36  import org.apache.directory.api.ldap.model.schema.SchemaManager;
37  import org.apache.directory.api.util.Network;
38  import org.apache.directory.api.util.Strings;
39  import org.apache.directory.server.core.annotations.AnnotationUtils;
40  import org.apache.directory.server.core.annotations.ApplyLdifFiles;
41  import org.apache.directory.server.core.annotations.ApplyLdifs;
42  import org.apache.directory.server.core.annotations.ContextEntry;
43  import org.apache.directory.server.core.annotations.CreateAuthenticator;
44  import org.apache.directory.server.core.annotations.CreateDS;
45  import org.apache.directory.server.core.annotations.CreateIndex;
46  import org.apache.directory.server.core.annotations.CreatePartition;
47  import org.apache.directory.server.core.annotations.LoadSchema;
48  import org.apache.directory.server.core.api.DirectoryService;
49  import org.apache.directory.server.core.api.DnFactory;
50  import org.apache.directory.server.core.api.interceptor.Interceptor;
51  import org.apache.directory.server.core.api.partition.Partition;
52  import org.apache.directory.server.core.authn.AuthenticationInterceptor;
53  import org.apache.directory.server.core.authn.Authenticator;
54  import org.apache.directory.server.core.authn.DelegatingAuthenticator;
55  import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition;
56  import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
57  import org.apache.directory.server.core.partition.impl.btree.mavibot.MavibotIndex;
58  import org.apache.directory.server.i18n.I18n;
59  import org.junit.runner.Description;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  
64  /**
65   * A Helper class used to create a DS from the annotations
66   * 
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   */
69  public final class DSAnnotationProcessor
70  {
71      /** A logger for this class */
72      private static final Logger LOG = LoggerFactory.getLogger( DSAnnotationProcessor.class );
73  
74  
75      private DSAnnotationProcessor()
76      {
77      }
78  
79  
80      /**
81       * Create the DirectoryService
82       * 
83       * @param dsBuilder The DirectoryService builder
84       * @return an instance of DirectoryService
85       * @throws Exception If the DirectoryService cannot be created
86       */
87      public static DirectoryService createDS( CreateDS dsBuilder )
88          throws Exception
89      {
90          if ( LOG.isDebugEnabled() )
91          {
92              LOG.debug( "Starting DS {}...", dsBuilder.name() );
93          }
94          
95          Class<?> factory = dsBuilder.factory();
96          DirectoryServiceFactory../org/apache/directory/server/core/factory/DirectoryServiceFactory.html#DirectoryServiceFactory">DirectoryServiceFactory dsf = ( DirectoryServiceFactory ) factory.newInstance();
97  
98          DirectoryService service = dsf.getDirectoryService();
99          service.setAccessControlEnabled( dsBuilder.enableAccessControl() );
100         service.setAllowAnonymousAccess( dsBuilder.allowAnonAccess() );
101         service.getChangeLog().setEnabled( dsBuilder.enableChangeLog() );
102 
103         dsf.init( dsBuilder.name() );
104 
105         for ( Class<?> interceptorClass : dsBuilder.additionalInterceptors() )
106         {
107             service.addLast( ( Interceptor ) interceptorClass.newInstance() );
108         }
109 
110         List<Interceptor> interceptorList = service.getInterceptors();
111 
112         if ( dsBuilder.authenticators().length != 0 )
113         {
114             AuthenticationInterceptor authenticationInterceptor = null;
115 
116             for ( Interceptor interceptor : interceptorList )
117             {
118                 if ( interceptor instanceof AuthenticationInterceptor )
119                 {
120                     authenticationInterceptor = ( AuthenticationInterceptor ) interceptor;
121                     break;
122                 }
123             }
124 
125             if ( authenticationInterceptor == null )
126             {
127                 throw new IllegalStateException(
128                     "authentication interceptor not found" );
129             }
130 
131             Set<Authenticator> authenticators = new HashSet<>();
132 
133             for ( CreateAuthenticator createAuthenticator : dsBuilder
134                 .authenticators() )
135             {
136                 Authenticator auth = createAuthenticator.type().newInstance();
137 
138                 if ( auth instanceof DelegatingAuthenticator )
139                 {
140                     DelegatingAuthenticator/org/apache/directory/server/core/authn/DelegatingAuthenticator.html#DelegatingAuthenticator">DelegatingAuthenticator dauth = ( DelegatingAuthenticator ) auth;
141                     
142                     String host = createAuthenticator.delegateHost();
143                     
144                     if ( Strings.isEmpty( host ) )
145                     {
146                         host = Network.LOOPBACK_HOSTNAME;
147                     }
148                     
149                     dauth.setDelegateHost( host );
150                     dauth.setDelegatePort( createAuthenticator.delegatePort() );
151                     dauth.setDelegateSsl( createAuthenticator.delegateSsl() );
152                     dauth.setDelegateTls( createAuthenticator.delegateTls() );
153                     dauth.setBaseDn( service.getDnFactory().create( createAuthenticator.baseDn() ) );
154                     dauth.setDelegateSslTrustManagerFQCN( createAuthenticator.delegateSslTrustManagerFQCN() );
155                     dauth.setDelegateTlsTrustManagerFQCN( createAuthenticator.delegateTlsTrustManagerFQCN() );
156                 }
157 
158                 authenticators.add( auth );
159             }
160 
161             authenticationInterceptor.setAuthenticators( authenticators );
162             authenticationInterceptor.init( service );
163         }
164 
165         service.setInterceptors( interceptorList );
166 
167         SchemaManager schemaManager = service.getSchemaManager();
168 
169         // process the schemas
170         for ( LoadSchema loadedSchema : dsBuilder.loadedSchemas() )
171         {
172             String schemaName = loadedSchema.name();
173             Boolean enabled = loadedSchema.enabled();
174 
175             // Check if the schema is loaded or not
176             boolean isLoaded = schemaManager.isSchemaLoaded( schemaName );
177 
178             if ( !isLoaded )
179             {
180                 // We have to load the schema, if it exists
181                 try
182                 {
183                     isLoaded = schemaManager.load( schemaName );
184                 }
185                 catch ( LdapUnwillingToPerformException lutpe )
186                 {
187                     // Cannot load the schema, it does not exists
188                     LOG.error( lutpe.getMessage() );
189                     continue;
190                 }
191             }
192 
193             if ( isLoaded )
194             {
195                 if ( enabled )
196                 {
197                     schemaManager.enable( schemaName );
198 
199                     if ( schemaManager.isDisabled( schemaName ) )
200                     {
201                         LOG.error( "Cannot enable {}", schemaName );
202                     }
203                 }
204                 else
205                 {
206                     schemaManager.disable( schemaName );
207 
208                     if ( schemaManager.isEnabled( schemaName ) )
209                     {
210                         LOG.error( "Cannot disable {}", schemaName );
211                     }
212                 }
213             }
214 
215             LOG.debug( "Loading schema {}, enabled= {}", schemaName, enabled );
216         }
217 
218         // Process the Partition, if any.
219         for ( CreatePartition createPartition : dsBuilder.partitions() )
220         {
221             Partition partition;
222 
223             // Determine the partition type
224             if ( createPartition.type() == Partition.class )
225             {
226                 // The annotation does not specify a specific partition type.
227                 // We use the partition factory to create partition and index
228                 // instances.
229                 PartitionFactory partitionFactory = dsf.getPartitionFactory();
230                 partition = partitionFactory.createPartition(
231                     schemaManager,
232                     service.getDnFactory(),
233                     createPartition.name(),
234                     createPartition.suffix(),
235                     createPartition.cacheSize(),
236                     new File( service.getInstanceLayout().getPartitionsDirectory(), createPartition.name() ) );
237 
238                 CreateIndex[] indexes = createPartition.indexes();
239 
240                 for ( CreateIndex createIndex : indexes )
241                 {
242                     partitionFactory.addIndex( partition,
243                         createIndex.attribute(), createIndex.cacheSize() );
244                 }
245 
246                 partition.initialize();
247             }
248             else
249             {
250                 // The annotation contains a specific partition type, we use
251                 // that type.
252                 Class<?>[] partypes = new Class[]
253                     { SchemaManager.class, DnFactory.class };
254                 Constructor<?> constructor = createPartition.type().getConstructor( partypes );
255                 partition = ( Partition ) constructor.newInstance( schemaManager, service.getDnFactory() );
256                 partition.setId( createPartition.name() );
257                 partition.setSuffixDn( new Dn( schemaManager, createPartition.suffix() ) );
258 
259                 if ( partition instanceof AbstractBTreePartition )
260                 {
261                     AbstractBTreePartitionche/directory/server/core/partition/impl/btree/AbstractBTreePartition.html#AbstractBTreePartition">AbstractBTreePartition btreePartition = ( AbstractBTreePartition ) partition;
262                     btreePartition.setCacheSize( createPartition.cacheSize() );
263                     btreePartition.setPartitionPath( new File( service
264                         .getInstanceLayout().getPartitionsDirectory(),
265                         createPartition.name() ).toURI() );
266 
267                     // Process the indexes if any
268                     CreateIndex[] indexes = createPartition.indexes();
269 
270                     for ( CreateIndex createIndex : indexes )
271                     {
272                         if ( createIndex.type() == MavibotIndex.class )
273                         {
274                             // Mavibot index
275                             MavibotIndex/core/partition/impl/btree/mavibot/MavibotIndex.html#MavibotIndex">MavibotIndex index = new MavibotIndex( createIndex.attribute(), false );
276 
277                             btreePartition.addIndexedAttributes( index );
278                         }
279                         else
280                         {
281                             // The annotation does not specify a specific index
282                             // type.
283                             // We use the generic index implementation.
284                             JdbmIndexver/core/partition/impl/btree/jdbm/JdbmIndex.html#JdbmIndex">JdbmIndex index = new JdbmIndex( createIndex.attribute(), false );
285 
286                             btreePartition.addIndexedAttributes( index );
287                         }
288                     }
289                 }
290             }
291 
292             partition.setSchemaManager( schemaManager );
293 
294             // Inject the partition into the DirectoryService
295             service.addPartition( partition );
296 
297             // Last, process the context entry
298             ContextEntry contextEntry = createPartition.contextEntry();
299 
300             if ( contextEntry != null )
301             {
302                 injectEntries( service, contextEntry.entryLdif() );
303             }
304         }
305 
306         return service;
307     }
308 
309 
310     /**
311      * Create a DirectoryService from a Unit test annotation
312      * 
313      * @param description The annotations containing the info from which we will create
314      *  the DS
315      * @return A valid DirectoryService
316      * @throws Exception If the DirectoryService instance can't be returned
317      */
318     public static DirectoryService getDirectoryService( Description description )
319         throws Exception
320     {
321         CreateDS dsBuilder = description.getAnnotation( CreateDS.class );
322 
323         if ( dsBuilder != null )
324         {
325             return createDS( dsBuilder );
326         }
327         else
328         {
329             LOG.debug( "No {} DS.", description.getDisplayName() );
330             return null;
331         }
332     }
333 
334 
335     /**
336      * Create a DirectoryService from an annotation. The @CreateDS annotation
337      * must be associated with either the method or the encapsulating class. We
338      * will first try to get the annotation from the method, and if there is
339      * none, then we try at the class level.
340      * 
341      * @return A valid DS
342      * @throws Exception If the DirectoryService instance can't be returned
343      */
344     public static DirectoryService getDirectoryService() throws Exception
345     {
346         Object instance = AnnotationUtils.getInstance( CreateDS.class );
347         CreateDS dsBuilder = null;
348 
349         if ( instance != null )
350         {
351             dsBuilder = ( CreateDS ) instance;
352 
353             // Ok, we have found a CreateDS annotation. Process it now.
354             return createDS( dsBuilder );
355         }
356 
357         throw new LdapException( I18n.err( I18n.ERR_114 ) );
358     }
359 
360 
361     /**
362      * injects an LDIF entry in the given DirectoryService
363      * 
364      * @param entry the LdifEntry to be injected
365      * @param service the DirectoryService
366      * @throws Exception If the entry cannot be injected
367      */
368     private static void injectEntry( LdifEntry entry, DirectoryService service )
369         throws LdapException
370     {
371         if ( entry.isChangeAdd() || entry.isLdifContent() )
372         {
373             service.getAdminSession().add(
374                 new DefaultEntry( service.getSchemaManager(), entry
375                     .getEntry() ) );
376         }
377         else if ( entry.isChangeModify() )
378         {
379             service.getAdminSession().modify( entry.getDn(),
380                 entry.getModifications() );
381         }
382         else
383         {
384             String message = I18n.err( I18n.ERR_117, entry.getChangeType() );
385             throw new LdapException( message );
386         }
387     }
388 
389 
390     /**
391      * injects the LDIF entries present in a LDIF file
392      * 
393      * @param clazz The class which classLoaded will be use to retrieve the resources
394      * @param service the DirectoryService
395      * @param ldifFiles array of LDIF file names (only )
396      * @throws Exception If we weren't able to inject LdifFiles
397      */
398     public static void injectLdifFiles( Class<?> clazz,
399         DirectoryService service, String[] ldifFiles ) throws Exception
400     {
401         if ( ( ldifFiles != null ) && ( ldifFiles.length > 0 ) )
402         {
403             for ( String ldifFile : ldifFiles )
404             {
405                 InputStream is = clazz.getClassLoader().getResourceAsStream(
406                     ldifFile );
407                 if ( is == null )
408                 {
409                     throw new FileNotFoundException( "LDIF file '" + ldifFile
410                         + "' not found." );
411                 }
412                 else
413                 {
414                     LdifReader ldifReader = new LdifReader( is );
415 
416                     for ( LdifEntry entry : ldifReader )
417                     {
418                         injectEntry( entry, service );
419                     }
420 
421                     ldifReader.close();
422                 }
423             }
424         }
425     }
426 
427 
428     /**
429      * Inject an ldif String into the server. Dn must be relative to the root.
430      * 
431      * @param service the directory service to use
432      * @param ldif the ldif containing entries to add to the server.
433      * @throws Exception if there is a problem adding the entries from the LDIF
434      */
435     public static void injectEntries( DirectoryService service, String ldif )
436         throws Exception
437     {
438         try ( LdifReader reader = new LdifReader() )
439         {
440             List<LdifEntry> entries = reader.parseLdif( ldif );
441     
442             for ( LdifEntry entry : entries )
443             {
444                 injectEntry( entry, service );
445             }
446         }
447     }
448 
449 
450     /**
451      * Load the schemas, and enable/disable them.
452      * 
453      * @param desc The description
454      * @param service The DirectoryService instance
455      */
456     public static void loadSchemas( Description desc, DirectoryService service )
457     {
458         if ( desc == null )
459         {
460             return;
461         }
462 
463         LoadSchema loadSchema = desc
464             .getAnnotation( LoadSchema.class );
465 
466         if ( loadSchema != null )
467         {
468             System.out.println( loadSchema );
469         }
470     }
471 
472 
473     /**
474      * Apply the LDIF entries to the given service
475      * 
476      * @param desc The description
477      * @param service The DirectoryService instance
478      * @throws Exception If we can't apply the ldifs
479      */
480     public static void applyLdifs( Description desc, DirectoryService service )
481         throws Exception
482     {
483         if ( desc == null )
484         {
485             return;
486         }
487 
488         ApplyLdifFiles applyLdifFiles = desc
489             .getAnnotation( ApplyLdifFiles.class );
490 
491         if ( applyLdifFiles != null )
492         {
493             LOG.debug( "Applying {} to {}", applyLdifFiles.value(),
494                 desc.getDisplayName() );
495             injectLdifFiles( applyLdifFiles.clazz(), service, applyLdifFiles.value() );
496         }
497 
498         ApplyLdifs applyLdifs = desc.getAnnotation( ApplyLdifs.class );
499 
500         if ( ( applyLdifs != null ) && ( applyLdifs.value() != null ) )
501         {
502             String[] ldifs = applyLdifs.value();
503 
504             String dnStart = "dn:";
505 
506             StringBuilder sb = new StringBuilder();
507 
508             for ( int i = 0; i < ldifs.length; )
509             {
510                 String s = ldifs[i++].trim();
511                 if ( s.startsWith( dnStart ) )
512                 {
513                     sb.append( s ).append( '\n' );
514 
515                     // read the rest of lines till we encounter Dn again
516                     while ( i < ldifs.length )
517                     {
518                         s = ldifs[i++];
519                         if ( !s.startsWith( dnStart ) )
520                         {
521                             sb.append( s ).append( '\n' );
522                         }
523                         else
524                         {
525                             break;
526                         }
527                     }
528 
529                     LOG.debug( "Applying {} to {}", sb, desc.getDisplayName() );
530                     injectEntries( service, sb.toString() );
531                     sb.setLength( 0 );
532 
533                     i--; // step up a line
534                 }
535             }
536         }
537     }
538 }