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