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.integ;
020    
021    
022    import java.lang.reflect.Field;
023    import java.util.UUID;
024    
025    import org.apache.commons.io.FileUtils;
026    import org.apache.directory.server.annotations.CreateLdapServer;
027    import org.apache.directory.server.core.DirectoryService;
028    import org.apache.directory.server.core.changelog.ChangeLog;
029    import org.apache.directory.server.core.factory.DSAnnotationProcessor;
030    import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
031    import org.apache.directory.server.core.factory.DirectoryServiceFactory;
032    import org.apache.directory.server.core.factory.PartitionFactory;
033    import org.apache.directory.server.factory.ServerAnnotationProcessor;
034    import org.apache.directory.server.i18n.I18n;
035    import org.apache.directory.server.kerberos.kdc.KdcServer;
036    import org.apache.directory.server.ldap.LdapServer;
037    import org.apache.directory.server.protocol.shared.transport.Transport;
038    import org.junit.Ignore;
039    import org.junit.runner.Description;
040    import org.junit.runner.notification.Failure;
041    import org.junit.runner.notification.RunNotifier;
042    import org.junit.runners.BlockJUnit4ClassRunner;
043    import org.junit.runners.model.FrameworkMethod;
044    import org.junit.runners.model.InitializationError;
045    import org.slf4j.Logger;
046    import org.slf4j.LoggerFactory;
047    
048    
049    /**
050     * The class responsible for running all the tests. t read the annotations, 
051     * initialize the DirectoryService, call each test and do the cleanup at the end.
052     * 
053     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
054     * @version $Rev$, $Date$
055     */
056    public class FrameworkRunner extends BlockJUnit4ClassRunner
057    {
058        /** A logger for this class */
059        private static final Logger LOG = LoggerFactory.getLogger( FrameworkRunner.class );
060    
061        /** The 'service' field in the run tests */
062        private static final String DIRECTORY_SERVICE_FIELD_NAME = "service";
063    
064        /** The 'ldapServer' field in the run tests */
065        private static final String LDAP_SERVER_FIELD_NAME = "ldapServer";
066    
067        /** The 'kdcServer' field in the run tests */
068        private static final String KDC_SERVER_FIELD_NAME = "kdcServer";
069    
070        /** The filed used to tell the test that it is run in a suite */
071        private static final String IS_RUN_IN_SUITE_FIELD_NAME = "isRunInSuite";
072    
073        /** The suite this class depend on, if any */
074        private FrameworkSuite suite;
075    
076        /** The DirectoryService for this class, if any */
077        private DirectoryService classDS;
078    
079        /** The LdapServer for this class, if any */
080        private LdapServer classLdapServer;
081    
082        /** The KdcServer for this class, if any */
083        private KdcServer classKdcServer;
084    
085    
086        /**
087         * Creates a new instance of FrameworkRunner.
088         */
089        public FrameworkRunner( Class<?> clazz ) throws InitializationError
090        {
091            super( clazz );
092        }
093    
094    
095        /**
096         * {@inheritDoc}
097         */
098        @Override
099        public void run( final RunNotifier notifier )
100        {
101            // Before running any test, check to see if we must create a class DS
102            // Get the LdapServerBuilder, if any
103            CreateLdapServer classLdapServerBuilder = getDescription().getAnnotation( CreateLdapServer.class );
104    
105            try
106            {
107                classDS = DSAnnotationProcessor.getDirectoryService( getDescription() );
108                long revision = 0L;
109                DirectoryService directoryService = null;
110    
111                if ( classDS != null )
112                {
113                    // We have a class DS defined, update it
114                    directoryService = classDS;
115    
116                    // Get the applyLdifs for each level and apply them
117                    if ( suite != null )
118                    {
119                        DSAnnotationProcessor.applyLdifs( suite.getDescription(), classDS );
120                    }
121    
122                    DSAnnotationProcessor.applyLdifs( getDescription(), classDS );
123                }
124                else
125                {
126                    // No class DS. Do we have a Suite ?
127                    if ( suite != null )
128                    {
129                        // yes. Do we have a suite DS ?
130                        directoryService = suite.getDirectoryService();
131    
132                        if ( directoryService != null )
133                        {
134                            // yes : apply the class LDIFs only, and tag for reversion
135                            revision = getCurrentRevision( directoryService );
136    
137                            // apply the class LDIFs
138                            DSAnnotationProcessor.applyLdifs( getDescription(), directoryService );
139                        }
140                        else
141                        {
142                            // No : define a default DS for the suite then
143                            DirectoryServiceFactory dsf = DefaultDirectoryServiceFactory.DEFAULT;
144    
145                            directoryService = dsf.getDirectoryService();
146                            // enable CL explicitly cause we are not using DSAnnotationProcessor
147                            directoryService.getChangeLog().setEnabled( true );
148    
149                            dsf.init( "default" + UUID.randomUUID().toString() );
150    
151                            // Stores it into the suite
152                            suite.setDirectoryService( directoryService );
153    
154                            // Apply the suite LDIF first
155                            DSAnnotationProcessor.applyLdifs( suite.getDescription(), directoryService );
156    
157                            // Then tag for reversion and apply the class LDIFs
158                            revision = getCurrentRevision( directoryService );
159    
160                            DSAnnotationProcessor.applyLdifs( getDescription(), directoryService );
161                        }
162                    }
163                    else
164                    {
165                        // No : define a default class DS then
166                        DirectoryServiceFactory dsf = DefaultDirectoryServiceFactory.DEFAULT;
167    
168                        directoryService = dsf.getDirectoryService();
169                        // enable CL explicitly cause we are not using DSAnnotationProcessor
170                        directoryService.getChangeLog().setEnabled( true );
171    
172                        dsf.init( "default" + UUID.randomUUID().toString() );
173    
174                        // Stores the defaultDS in the classDS
175                        classDS = directoryService;
176    
177                        // Apply the class LDIFs
178                        DSAnnotationProcessor.applyLdifs( getDescription(), directoryService );
179                    }
180                }
181    
182                // check if it has a LdapServerBuilder
183                // then use the DS created above
184                if ( classLdapServerBuilder != null )
185                {
186                    int minPort = 0;
187    
188                    if ( suite != null )
189                    {
190                        LdapServer suiteServer = suite.getLdapServer();
191    
192                        if ( suiteServer != null )
193                        {
194                            for ( Transport transport : suiteServer.getTransports() )
195                            {
196                                if ( minPort <= transport.getPort() )
197                                {
198                                    minPort = transport.getPort();
199                                }
200                            }
201                        }
202                    }
203    
204                    classLdapServer = ServerAnnotationProcessor.getLdapServer( getDescription(), directoryService,
205                        minPort + 1 );
206                }
207                else if ( ( suite != null ) && ( suite.getLdapServer() != null ) )
208                {
209                    classLdapServer = suite.getLdapServer();
210                    
211                    // set directoryService only if there is no class level DS
212                    if ( directoryService == null )
213                    {
214                        directoryService = classLdapServer.getDirectoryService();
215                    }
216                    
217                    // no need to inject the LDIF data that would have been done above
218                    // if ApplyLdifs is present
219                }
220    
221                if ( classKdcServer == null )
222                {
223                    classKdcServer = ServerAnnotationProcessor.getKdcServer( getDescription(), directoryService, 0 );
224                }
225                else if ( suite != null )
226                {
227                    // TODO add suite level KdcServer support
228                }
229    
230                if ( suite == null )
231                {
232                    // print out information which partition factory we use
233                    PartitionFactory partitionFactory = DefaultDirectoryServiceFactory.DEFAULT.getPartitionFactory();
234                    System.out.println( "Using partition factory " + partitionFactory.getClass().getSimpleName() );
235                }
236    
237                // Now run the class
238                super.run( notifier );
239    
240                if ( classLdapServer != null )
241                {
242                    if ( ( suite == null ) || ( suite.getLdapServer() != classLdapServer ) )
243                    {
244                        classLdapServer.stop();
245                    }
246                }
247    
248                if ( classKdcServer != null )
249                {
250                    classKdcServer.stop();
251                }
252    
253                // cleanup classService if it is not the same as suite service or
254                // it is not null (this second case happens in the absence of a suite)
255                if ( classDS != null )
256                {
257                    LOG.debug( "Shuting down DS for {}", classDS.getInstanceId() );
258                    classDS.shutdown();
259                    FileUtils.deleteDirectory( classDS.getWorkingDirectory() );
260                }
261                else
262                {
263                    // Revert the ldifs
264                    // We use a class or suite DS, just revert the current test's modifications
265                    revert( directoryService, revision );
266                }
267            }
268            catch ( Exception e )
269            {
270                LOG.error( I18n.err( I18n.ERR_181, getTestClass().getName() ) );
271                LOG.error( e.getLocalizedMessage() );
272                notifier.fireTestFailure( new Failure( getDescription(), e ) );
273            }
274        }
275    
276    
277        /**
278         * {@inheritDoc}
279         */
280        @Override
281        protected void runChild( FrameworkMethod method, RunNotifier notifier )
282        {
283            // Don't run the test if the @Ignored annotation is used
284            if ( method.getAnnotation( Ignore.class ) != null )
285            {
286                Description description = describeChild( method );
287                notifier.fireTestIgnored( description );
288                return;
289            }
290    
291            // Get the applyLdifs for each level
292            Description suiteDescription = null;
293    
294            if ( suite != null )
295            {
296                suiteDescription = suite.getDescription();
297            }
298    
299            Description classDescription = getDescription();
300            Description methodDescription = describeChild( method );
301    
302            // Ok, ready to run the test
303            try
304            {
305                DirectoryService directoryService = null;
306    
307                // Set the revision to 0, we will revert only if it's set to another value
308                long revision = 0L;
309    
310                // Check if this method has a dedicated DSBuilder
311                DirectoryService methodDS = DSAnnotationProcessor.getDirectoryService( methodDescription );
312    
313                // give #1 priority to method level DS if present
314                if ( methodDS != null )
315                {
316                    // Apply all the LDIFs
317                    DSAnnotationProcessor.applyLdifs( suiteDescription, methodDS );
318                    DSAnnotationProcessor.applyLdifs( classDescription, methodDS );
319                    DSAnnotationProcessor.applyLdifs( methodDescription, methodDS );
320    
321                    directoryService = methodDS;
322                }
323                else if ( classDS != null )
324                {
325                    directoryService = classDS;
326    
327                    // apply the method LDIFs, and tag for reversion
328                    revision = getCurrentRevision( directoryService );
329    
330                    DSAnnotationProcessor.applyLdifs( methodDescription, directoryService );
331                }
332                // we don't support method level LdapServer so
333                // we check for the presence of Class level LdapServer first 
334                else if ( classLdapServer != null )
335                {
336                    directoryService = classLdapServer.getDirectoryService();
337    
338                    revision = getCurrentRevision( directoryService );
339    
340                    DSAnnotationProcessor.applyLdifs( methodDescription, directoryService );
341                }
342                else if ( classKdcServer != null )
343                {
344                    directoryService = classKdcServer.getDirectoryService();
345    
346                    revision = getCurrentRevision( directoryService );
347    
348                    DSAnnotationProcessor.applyLdifs( methodDescription, directoryService );
349                }
350                else if ( suite != null )
351                {
352                    directoryService = suite.getDirectoryService();
353    
354                    // apply the method LDIFs, and tag for reversion
355                    revision = getCurrentRevision( directoryService );
356    
357                    DSAnnotationProcessor.applyLdifs( methodDescription, directoryService );
358                }
359    
360                // At this point, we know which service to use.
361                // Inject it into the class
362                Field dirServiceField = getTestClass().getJavaClass().getField( DIRECTORY_SERVICE_FIELD_NAME );
363                dirServiceField.set( getTestClass().getJavaClass(), directoryService );
364    
365                // if we run this class in a suite, tell it to the test
366                Field runInSuiteField = getTestClass().getJavaClass().getField( IS_RUN_IN_SUITE_FIELD_NAME );
367                runInSuiteField.set( getTestClass().getJavaClass(), suite != null );
368    
369                Field ldapServerField = getTestClass().getJavaClass().getField( LDAP_SERVER_FIELD_NAME );
370    
371                dirServiceField.set( getTestClass().getJavaClass(), directoryService );
372    
373                DirectoryService oldLdapServerDirService = null;
374                DirectoryService oldKdcServerDirService = null;
375                
376                if ( classLdapServer != null )
377                {
378                    oldLdapServerDirService = classLdapServer.getDirectoryService();
379                    
380                    // setting the directoryService is required to inject the correct level DS instance in the class or suite level LdapServer
381                    classLdapServer.setDirectoryService( directoryService );
382                    
383                    ldapServerField.set( getTestClass().getJavaClass(), classLdapServer );
384                }
385                else if ( classKdcServer != null )
386                {
387                    oldKdcServerDirService = classKdcServer.getDirectoryService();
388                    
389                    // setting the directoryService is required to inject the correct level DS instance in the class or suite level KdcServer
390                    classKdcServer.setDirectoryService( directoryService );
391                    
392                    Field kdcServerField = getTestClass().getJavaClass().getField( KDC_SERVER_FIELD_NAME );
393                    kdcServerField.set( getTestClass().getJavaClass(), classKdcServer );
394                }
395    
396                // Run the test
397                super.runChild( method, notifier );
398    
399                if ( oldLdapServerDirService != null )
400                {
401                    classLdapServer.setDirectoryService( oldLdapServerDirService );
402                }
403                
404                if ( oldKdcServerDirService != null )
405                {
406                    classKdcServer.setDirectoryService( oldKdcServerDirService );
407                }
408                
409                // Cleanup the methodDS if it has been created
410                if ( methodDS != null )
411                {
412                    LOG.debug( "Shuting down DS for {}", methodDS.getInstanceId() );
413                    methodDS.shutdown();
414                    FileUtils.deleteDirectory( methodDS.getWorkingDirectory() );
415                }
416                else
417                {
418                    // We use a class or suite DS, just revert the current test's modifications
419                    revert( directoryService, revision );
420                }
421            }
422            catch ( Exception e )
423            {
424                LOG.error( I18n.err( I18n.ERR_182, method.getName() ) );
425                LOG.error( "", e );
426                notifier.fireTestFailure( new Failure( getDescription(), e ) );
427            }
428        }
429    
430    
431        /**
432         * Set the Suite reference into this class
433         *
434         * @param suite The suite this classd is contained into
435         */
436        public void setSuite( FrameworkSuite suite )
437        {
438            this.suite = suite;
439        }
440    
441    
442        /**
443         * @return The Suite this class is contained nto, if any
444         */
445        public FrameworkSuite getSuite()
446        {
447            return suite;
448        }
449    
450    
451        private long getCurrentRevision( DirectoryService dirService ) throws Exception
452        {
453            if ( ( dirService != null ) && ( dirService.getChangeLog().isEnabled() ) )
454            {
455                long revision = dirService.getChangeLog().getCurrentRevision();
456                LOG.debug( "Create revision {}", revision );
457    
458                return revision;
459            }
460    
461            return 0;
462        }
463    
464    
465        private void revert( DirectoryService dirService, long revision ) throws Exception
466        {
467            if ( dirService == null )
468            {
469                return;
470            }
471    
472            ChangeLog cl = dirService.getChangeLog();
473            if ( cl.isEnabled() && ( revision < cl.getCurrentRevision() ) )
474            {
475                LOG.debug( "Revert revision {}", revision );
476                dirService.revert( revision );
477            }
478        }
479    }