001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.directory.server.core.factory;
020    
021    
022    import java.io.File;
023    import java.io.FileNotFoundException;
024    import java.io.InputStream;
025    import java.lang.reflect.Method;
026    import java.util.List;
027    
028    import javax.naming.NamingException;
029    
030    import org.apache.directory.server.core.DirectoryService;
031    import org.apache.directory.server.core.annotations.ApplyLdifFiles;
032    import org.apache.directory.server.core.annotations.ApplyLdifs;
033    import org.apache.directory.server.core.annotations.ContextEntry;
034    import org.apache.directory.server.core.annotations.CreateDS;
035    import org.apache.directory.server.core.annotations.CreateIndex;
036    import org.apache.directory.server.core.annotations.CreatePartition;
037    import org.apache.directory.server.core.interceptor.Interceptor;
038    import org.apache.directory.server.core.partition.Partition;
039    import org.apache.directory.server.core.partition.impl.btree.BTreePartition;
040    import org.apache.directory.server.i18n.I18n;
041    import org.apache.directory.server.xdbm.GenericIndex;
042    import org.apache.directory.server.xdbm.Index;
043    import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
044    import org.apache.directory.shared.ldap.ldif.LdifEntry;
045    import org.apache.directory.shared.ldap.ldif.LdifReader;
046    import org.junit.runner.Description;
047    import org.slf4j.Logger;
048    import org.slf4j.LoggerFactory;
049    
050    
051    /**
052     * A Helper class used to create a DS from the annotations
053     *
054     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
055     * @version $Rev$, $Date$
056     */
057    public class DSAnnotationProcessor
058    {
059        /** A logger for this class */
060        private static final Logger LOG = LoggerFactory.getLogger( DSAnnotationProcessor.class );
061    
062    
063        /**
064         * Create the DirectoryService
065         */
066        private static DirectoryService createDS( CreateDS dsBuilder ) throws Exception
067        {
068            LOG.debug( "Starting DS {}...", dsBuilder.name() );
069            Class<?> factory = dsBuilder.factory();
070            DirectoryServiceFactory dsf = ( DirectoryServiceFactory ) factory.newInstance();
071    
072            DirectoryService service = dsf.getDirectoryService();
073            service.setAccessControlEnabled( dsBuilder.enableAccessControl() );
074            service.setAllowAnonymousAccess( dsBuilder.allowAnonAccess() );
075            service.getChangeLog().setEnabled( dsBuilder.enableChangeLog() );
076    
077            List<Interceptor> interceptorList = service.getInterceptors();
078            for ( Class<?> interceptorClass : dsBuilder.additionalInterceptors() )
079            {
080                interceptorList.add( ( Interceptor ) interceptorClass.newInstance() );
081            }
082    
083            service.setInterceptors( interceptorList );
084    
085            dsf.init( dsBuilder.name() );
086    
087            // Process the Partition, if any.
088            for ( CreatePartition createPartition : dsBuilder.partitions() )
089            {
090                Partition partition;
091    
092                // Determine the partition type
093                if ( createPartition.type() == Partition.class )
094                {
095                    // The annotation does not specify a specific partition type.
096                    // We use the partition factory to create partition and index instances.
097                    PartitionFactory partitionFactory = dsf.getPartitionFactory();
098                    partition = partitionFactory.createPartition( createPartition.name(), createPartition.suffix(),
099                        createPartition.cacheSize(), new File( service.getWorkingDirectory(), createPartition.name() ) );
100    
101                    CreateIndex[] indexes = createPartition.indexes();
102                    for ( CreateIndex createIndex : indexes )
103                    {
104                        partitionFactory.addIndex( partition, createIndex.attribute(), createIndex.cacheSize() );
105                    }
106                }
107                else
108                {
109                    // The annotation contains a specific partition type, we use that type.
110                    partition = createPartition.type().newInstance();
111                    partition.setId( createPartition.name() );
112                    partition.setSuffix( createPartition.suffix() );
113    
114                    if ( partition instanceof BTreePartition<?> )
115                    {
116                        BTreePartition<?> btreePartition = ( BTreePartition<?> ) partition;
117                        btreePartition.setCacheSize( createPartition.cacheSize() );
118                        btreePartition.setPartitionDir( new File( service.getWorkingDirectory(), createPartition.name() ) );
119    
120                        // Process the indexes if any
121                        CreateIndex[] indexes = createPartition.indexes();
122    
123                        for ( CreateIndex createIndex : indexes )
124                        {
125                            Index index;
126                            if ( createIndex.type() == Index.class )
127                            {
128                                // The annotation does not specify a specific index type.
129                                // We use the generic index implementation.
130                                index = new GenericIndex( createIndex.attribute(), createIndex.cacheSize() );
131                            }
132                            else
133                            {
134                                // The annotation contains a specific index type, we use that type.
135                                index = createIndex.type().newInstance();
136                                index.setAttributeId( createIndex.attribute() );
137                                index.setCacheSize( createIndex.cacheSize() );
138                            }
139                            btreePartition.addIndexedAttributes( index );
140                        }
141                    }
142                }
143    
144                partition.setSchemaManager( service.getSchemaManager() );
145    
146                // Inject the partition into the DirectoryService
147                service.addPartition( partition );
148    
149                // Last, process the context entry
150                ContextEntry contextEntry = createPartition.contextEntry();
151    
152                if ( contextEntry != null )
153                {
154                    injectEntries( service, contextEntry.entryLdif() );
155                }
156            }
157    
158            return service;
159        }
160    
161    
162        /**
163         * Create a DirectoryService from a Unit test annotation
164         *
165         * @param description The annotations containing the info from which we will create the DS
166         * @return A valid DS
167         */
168        public static DirectoryService getDirectoryService( Description description ) throws Exception
169        {
170            CreateDS dsBuilder = description.getAnnotation( CreateDS.class );
171    
172            if ( dsBuilder != null )
173            {
174                return createDS( dsBuilder );
175            }
176            else
177            {
178                LOG.debug( "No {} DS.", description.getDisplayName() );
179                return null;
180            }
181        }
182    
183    
184        /**
185         * Create a DirectoryService from an annotation. The @CreateDS annotation must
186         * be associated with either the method or the encapsulating class. We will first
187         * try to get the annotation from the method, and if there is none, then we try
188         * at the class level. 
189         *
190         * @return A valid DS
191         */
192        public static DirectoryService getDirectoryService() throws Exception
193        {
194            CreateDS dsBuilder = null;
195    
196            // Get the caller by inspecting the stackTrace
197            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
198    
199            // In Java5 the 0th stacktrace element is: java.lang.Thread.dumpThreads(Native Method)
200            int index = stackTrace[0].getMethodName().equals( "dumpThreads" ) ? 3 : 2;
201    
202            // Get the enclosing class
203            Class<?> classCaller = Class.forName( stackTrace[index].getClassName() );
204    
205            // Get the current method
206            String methodCaller = stackTrace[index].getMethodName();
207    
208            // Check if we have any annotation associated with the method
209            Method[] methods = classCaller.getMethods();
210    
211            for ( Method method : methods )
212            {
213                if ( methodCaller.equals( method.getName() ) )
214                {
215                    dsBuilder = method.getAnnotation( CreateDS.class );
216    
217                    if ( dsBuilder != null )
218                    {
219                        break;
220                    }
221                }
222            }
223    
224            // No : look at the class level
225            if ( dsBuilder == null )
226            {
227                dsBuilder = classCaller.getAnnotation( CreateDS.class );
228            }
229    
230            // Ok, we have found a CreateDS annotation. Process it now.
231            return createDS( dsBuilder );
232        }
233    
234    
235        /**
236         * injects an LDIF entry in the given DirectoryService
237         * 
238         * @param entry the LdifEntry to be injected
239         * @param service the DirectoryService
240         * @throws Exception
241         */
242        private static void injectEntry( LdifEntry entry, DirectoryService service ) throws Exception
243        {
244            if ( entry.isChangeAdd() )
245            {
246                service.getAdminSession().add( new DefaultServerEntry( service.getSchemaManager(), entry.getEntry() ) );
247            }
248            else if ( entry.isChangeModify() )
249            {
250                service.getAdminSession().modify( entry.getDn(), entry.getModificationItems() );
251            }
252            else
253            {
254                String message = I18n.err( I18n.ERR_117, entry.getChangeType() );
255                throw new NamingException( message );
256            }
257        }
258    
259    
260        /**
261         * injects the LDIF entries present in a LDIF file
262         * 
263         * @param service the DirectoryService 
264         * @param ldifFiles the array of LDIF file names (only )
265         * @throws Exception
266         */
267        public static void injectLdifFiles( Class<?> clazz, DirectoryService service, String[] ldifFiles ) throws Exception
268        {
269            if ( ( ldifFiles != null ) && ( ldifFiles.length > 0 ) )
270            {
271                for ( String ldifFile : ldifFiles )
272                {
273                    InputStream is = clazz.getClassLoader().getResourceAsStream( ldifFile );
274                    if ( is == null )
275                    {
276                        throw new FileNotFoundException( "LDIF file '" + ldifFile + "' not found." );
277                    }
278                    else
279                    {
280                        try
281                        {
282                            LdifReader ldifReader = new LdifReader( is );
283    
284                            for ( LdifEntry entry : ldifReader )
285                            {
286                                injectEntry( entry, service );
287                            }
288    
289                            ldifReader.close();
290                        }
291                        catch ( Exception e )
292                        {
293                            LOG.error( I18n.err( I18n.ERR_80, ldifFile, e.getLocalizedMessage() ) );
294                        }
295                    }
296                }
297            }
298        }
299    
300    
301        /**
302         * Inject an ldif String into the server. DN must be relative to the
303         * root.
304         *
305         * @param service the directory service to use 
306         * @param ldif the ldif containing entries to add to the server.
307         * @throws NamingException if there is a problem adding the entries from the LDIF
308         */
309        public static void injectEntries( DirectoryService service, String ldif ) throws Exception
310        {
311            LdifReader reader = new LdifReader();
312            List<LdifEntry> entries = reader.parseLdif( ldif );
313    
314            for ( LdifEntry entry : entries )
315            {
316                injectEntry( entry, service );
317            }
318    
319            // And close the reader
320            reader.close();
321        }
322    
323    
324        /**
325         * Apply the LDIF entries to the given service
326         */
327        public static void applyLdifs( Description desc, DirectoryService service ) throws Exception
328        {
329            if ( desc == null )
330            {
331                return;
332            }
333    
334            ApplyLdifFiles applyLdifFiles = desc.getAnnotation( ApplyLdifFiles.class );
335    
336            if ( applyLdifFiles != null )
337            {
338                LOG.debug( "Applying {} to {}", applyLdifFiles.value(), desc.getDisplayName() );
339                injectLdifFiles( desc.getClass(), service, applyLdifFiles.value() );
340            }
341    
342            ApplyLdifs applyLdifs = desc.getAnnotation( ApplyLdifs.class );
343    
344            if ( ( applyLdifs != null ) && ( applyLdifs.value() != null ) )
345            {
346                String[] ldifs = applyLdifs.value();
347    
348                String DN_START = "dn:";
349    
350                StringBuilder sb = new StringBuilder();
351    
352                for ( int i = 0; i < ldifs.length; )
353                {
354                    String s = ldifs[i++].trim();
355                    if ( s.startsWith( DN_START ) )
356                    {
357                        sb.append( s ).append( '\n' );
358    
359                        // read the rest of lines till we encounter DN again
360                        while ( i < ldifs.length )
361                        {
362                            s = ldifs[i++];
363                            if ( !s.startsWith( DN_START ) )
364                            {
365                                sb.append( s ).append( '\n' );
366                            }
367                            else
368                            {
369                                break;
370                            }
371                        }
372    
373                        LOG.debug( "Applying {} to {}", sb, desc.getDisplayName() );
374                        injectEntries( service, sb.toString() );
375                        sb.setLength( 0 );
376    
377                        i--; // step up a line
378                    }
379                }
380            }
381        }
382    }