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 021import java.lang.reflect.AnnotatedElement; 022import java.lang.reflect.Field; 023import java.util.UUID; 024 025import org.apache.directory.api.ldap.model.name.DefaultDnFactory; 026import org.apache.directory.api.util.FileUtils; 027import org.apache.directory.server.annotations.CreateLdapServer; 028import org.apache.directory.server.core.annotations.CreateDS; 029import org.apache.directory.server.core.api.DirectoryService; 030import org.apache.directory.server.core.api.changelog.ChangeLog; 031import org.apache.directory.server.core.factory.DSAnnotationProcessor; 032import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory; 033import org.apache.directory.server.core.factory.DirectoryServiceFactory; 034import org.apache.directory.server.core.factory.PartitionFactory; 035import org.apache.directory.server.factory.ServerAnnotationProcessor; 036import org.apache.directory.server.i18n.I18n; 037import org.apache.directory.server.ldap.LdapServer; 038import org.junit.jupiter.api.Disabled; 039import org.junit.jupiter.api.extension.AfterAllCallback; 040import org.junit.jupiter.api.extension.AfterEachCallback; 041import org.junit.jupiter.api.extension.BeforeAllCallback; 042import org.junit.jupiter.api.extension.BeforeEachCallback; 043import org.junit.jupiter.api.extension.ExtensionContext; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047public class ApacheDSTestExtension implements BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback 048{ 049 /** A logger for this class */ 050 private static final Logger LOG = LoggerFactory.getLogger( ApacheDSTestExtension.class ); 051 052 public static final String CLASS_DS = "classDirectoryService"; 053 public static final String CLASS_LS = "classLdapServer"; 054 public static final String METHOD_DS = "methodDirectoryService"; 055 public static final String METHOD_LS = "methodLdapServer"; 056 public static final String CURRENT_DS = "directoryService"; 057 public static final String CURRENT_LS = "ldapServer"; 058 public static final String REVISION = "revision"; 059 060 private DirectoryService getDirectoryService( ExtensionContext context, String fieldName ) 061 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 062 { 063 Class<?> testClass = context.getTestClass().get(); 064 Field field = testClass.getField( fieldName ); 065 066 if ( field != null ) 067 { 068 return ( DirectoryService ) field.get( testClass ); 069 } 070 else 071 { 072 return null; 073 } 074 } 075 076 077 private void setDirectoryService( ExtensionContext context, String fieldName, DirectoryService directoryService ) 078 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 079 { 080 Class<?> testClass = context.getTestClass().get(); 081 Field field = testClass.getField( fieldName ); 082 field.set( null, directoryService ); 083 } 084 085 086 private LdapServer getLdapServer( ExtensionContext context, String fieldName ) 087 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 088 { 089 Class<?> testClass = context.getTestClass().get(); 090 Field field = testClass.getField( fieldName ); 091 092 if ( field != null ) 093 { 094 return ( LdapServer ) field.get( testClass ); 095 } 096 else 097 { 098 return null; 099 } 100 } 101 102 103 private void setLdapServer( ExtensionContext context, String fieldName, LdapServer ldapServer ) 104 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 105 { 106 Class<?> testClass = context.getTestClass().get(); 107 Field field = testClass.getField( fieldName ); 108 field.set( null, ldapServer ); 109 } 110 111 112 private long getRevision( ExtensionContext context, String fieldName ) 113 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 114 { 115 Class<?> testClass = context.getTestClass().get(); 116 Field field = testClass.getField( fieldName ); 117 118 if ( field != null ) 119 { 120 return ( long ) field.get( testClass ); 121 } 122 else 123 { 124 return 0L; 125 } 126 } 127 128 129 private void setRevision( ExtensionContext context, long revision ) 130 throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException 131 { 132 Class<?> testClass = context.getTestClass().get(); 133 Field field = testClass.getField( REVISION ); 134 field.set( null, revision ); 135 } 136 137 138 @Override 139 public void beforeAll( ExtensionContext context ) throws Exception 140 { 141 // Don't run the test if the @Disabled annotation is used 142 if ( context.getTestClass().get().getAnnotation( Disabled.class ) != null ) 143 { 144 return; 145 } 146 147 try 148 { 149 // First check if we have a classDS. If so, we create it, and 150 // inject the LDIFs into it. 151 // Last, we store the classDS instance into the test class 152 AnnotatedElement annotations = context.getTestClass().get(); 153 154 CreateDS dsBuilder = annotations.getDeclaredAnnotation( CreateDS.class ); 155 156 DirectoryService classDS = DSAnnotationProcessor.getDirectoryService( dsBuilder ); 157 158 if ( classDS == null ) 159 { 160 // No : define a default class DS then 161 DirectoryServiceFactory dsf = DefaultDirectoryServiceFactory.class.newInstance(); 162 163 classDS = dsf.getDirectoryService(); 164 165 // enable CL explicitly cause we are not using DSAnnotationProcessor 166 classDS.getChangeLog().setEnabled( true ); 167 168 dsf.init( "default" + UUID.randomUUID().toString() ); 169 170 // Load the schemas 171 DSAnnotationProcessor.loadSchemas( context, classDS ); 172 173 dsf.getDirectoryService().getLdapCodecService().setDnfactory( new DefaultDnFactory( dsf.getDirectoryService().getSchemaManager(), 1000 ) ); 174 } 175 176 // Apply the class LDIFs 177 DSAnnotationProcessor.applyLdifs( annotations, context.getDisplayName(), classDS ); 178 179 setDirectoryService( context, CLASS_DS, classDS ); 180 181 // check if it has a LdapServerBuilder 182 CreateLdapServer classLdapServerBuilder = annotations.getDeclaredAnnotation( CreateLdapServer.class ); 183 184 if ( classLdapServerBuilder != null ) 185 { 186 LdapServer classLdapServer = ServerAnnotationProcessor.createLdapServer( annotations, classDS ); 187 setLdapServer( context, CLASS_LS, classLdapServer ); 188 } 189 190 // print out information which partition factory we use 191 DirectoryServiceFactory dsFactory = DefaultDirectoryServiceFactory.class.newInstance(); 192 PartitionFactory partitionFactory = dsFactory.getPartitionFactory(); 193 LOG.debug( "Using partition factory {}", partitionFactory.getClass().getSimpleName() ); 194 195 } 196 catch ( Exception e ) 197 { 198 e.printStackTrace(); 199 } 200 finally 201 { 202 // help GC to get rid of the directory service with all its references 203 System.out.println( "" ); 204 } 205 } 206 207 208 public void afterAll( ExtensionContext context ) 209 { 210 try 211 { 212 LdapServer classLdapServer = getLdapServer( context, CLASS_LS ); 213 214 if ( classLdapServer != null ) 215 { 216 classLdapServer.stop(); 217 } 218 219 // cleanup classService if it is not the same as suite service or 220 // it is not null (this second case happens in the absence of a suite) 221 DirectoryService classDS = getDirectoryService( context, CLASS_DS ); 222 223 if ( classDS != null ) 224 { 225 LOG.debug( "Shuting down DS for {}", classDS.getInstanceId() ); 226 classDS.shutdown(); 227 FileUtils.deleteDirectory( classDS.getInstanceLayout().getInstanceDirectory() ); 228 } 229 else 230 { 231 // Revert the ldifs 232 // We use a class or suite DS, just revert the current test's modifications 233 long revision = getRevision( context, REVISION ); 234 235 revert( classDS, revision ); 236 } 237 } 238 catch ( Exception e ) 239 { 240 e.printStackTrace(); 241 LOG.error( I18n.err( I18n.ERR_181, context.getTestClass().get().getName() ) ); 242 LOG.error( e.getLocalizedMessage() ); 243 } 244 } 245 246 247 /** 248 * We may have a method DS. In this case, it will be used for the method, the class 249 * DS will simply be ignored. 250 * 251 * As we don't allow method LdapServers, we will use the class LS if there is one declared. 252 * 253 * In any case, the method test is fully standalone. 254 * 255 * Note that we will apply all the ldifs, including the class level ones. 256 */ 257 @Override 258 public void beforeEach( ExtensionContext context ) throws Exception 259 { 260 // Don't run the test if the @Disabled annotation is used 261 if ( context.getTestMethod().get().getAnnotation( Disabled.class ) != null ) 262 { 263 return; 264 } 265 266 // First process the DS 267 DirectoryService directoryService = processDS( context ); 268 269 // Then process the LS 270 processLS( context, directoryService ); 271 } 272 273 274 private DirectoryService processDS( ExtensionContext context ) throws Exception 275 { 276 277 // Get the applyLdifs for each level 278 AnnotatedElement classAnnotation = context.getTestClass().get(); 279 AnnotatedElement methodAnnotation = context.getTestMethod().get(); 280 281 // Before running any test, check to see if we must create a method DS 282 CreateDS methodDsBuilder = methodAnnotation.getDeclaredAnnotation( CreateDS.class ); 283 DirectoryService classDS = getDirectoryService( context, CLASS_DS ); 284 285 // The current service is the classDS 286 DirectoryService service = classDS; 287 288 if ( methodDsBuilder != null ) 289 { 290 // Build the DS server now 291 try 292 { 293 // Instanciate the method DS 294 DirectoryService methodDS = DSAnnotationProcessor.getDirectoryService( methodDsBuilder ); 295 296 // give #1 priority to method level DS if present 297 // Apply all the LDIFs, class and method ones 298 299 // we don't support method level LdapServer so 300 // we check for the presence of Class level LdapServer first 301 setDirectoryService( context, METHOD_DS, methodDS ); 302 303 // Change the current DS to methodDS 304 service = methodDS; 305 306 // Ands apply LDIFs to the method DS 307 DSAnnotationProcessor.applyLdifs( classAnnotation, context.getDisplayName(), service ); 308 } 309 catch ( Exception e ) 310 { 311 } 312 } 313 314 // Apply the method LDIF into the current DS 315 DSAnnotationProcessor.applyLdifs( methodAnnotation, context.getDisplayName(), service ); 316 317 // Get current revision 318 if ( ( service != null ) && ( service.getChangeLog().isEnabled() ) ) 319 { 320 long revision = service.getChangeLog().getCurrentRevision(); 321 setRevision( context, revision ); 322 } 323 324 // Now, set the current service 325 setDirectoryService( context, CURRENT_DS, service ); 326 327 return service; 328 } 329 330 331 private void processLS( ExtensionContext context, DirectoryService directoryService ) throws Exception 332 { 333 334 // Get the applyLdifs for each level 335 AnnotatedElement methodAnnotation = context.getTestMethod().get(); 336 337 // Before running any test, check to see if we must create a method LS 338 LdapServer ldapServer = getLdapServer( context, CLASS_LS ); 339 340 CreateLdapServer methodLsBuilder = methodAnnotation.getDeclaredAnnotation( CreateLdapServer.class ); 341 342 // check if it has a LdapServerBuilder 343 if ( methodLsBuilder != null ) 344 { 345 LdapServer methodLdapServer = ServerAnnotationProcessor.createLdapServer( methodAnnotation, getDirectoryService( context, CURRENT_DS ) ); 346 setLdapServer( context, METHOD_LS, methodLdapServer ); 347 348 ldapServer = methodLdapServer; 349 } 350 351 if ( ldapServer != null ) 352 { 353 // We may have a LdapServer, but we need to associated it with the proper DS 354 DirectoryService currentDS = getDirectoryService( context, CURRENT_DS ); 355 356 ldapServer.setDirectoryService( currentDS ); 357 358 // last, not least, set the current LS 359 setLdapServer( context, CURRENT_LS, ldapServer ); 360 } 361 } 362 363 364 @Override 365 public void afterEach( ExtensionContext context ) throws Exception 366 { 367 DirectoryService methodDS = getDirectoryService( context, METHOD_DS ); 368 DirectoryService classDS = getDirectoryService( context, CLASS_DS ); 369 LdapServer methodLS = getLdapServer( context, METHOD_LS ); 370 long revision = getRevision( context, REVISION ); 371 372 try 373 { 374 // Cleanup the methodDS if it has been created 375 if ( methodDS != null ) 376 { 377 LOG.debug( "Shuting down DS for {}", methodDS.getInstanceId() ); 378 methodDS.shutdown(); 379 FileUtils.deleteDirectory( methodDS.getInstanceLayout().getInstanceDirectory() ); 380 setDirectoryService( context, METHOD_DS, null ); 381 setLdapServer( context, METHOD_LS, null ); 382 } 383 else 384 { 385 // We use a class or suite DS, just revert the current test's modifications 386 revert( classDS, revision ); 387 } 388 389 if ( methodLS != null ) 390 { 391 methodLS.stop(); 392 } 393 } 394 catch ( Exception e ) 395 { 396 397 } 398 } 399 400 401 /** 402 * Get the lower port out of all the transports 403 */ 404 private int getMinPort() 405 { 406 return 0; 407 } 408 409 410 private void revert( DirectoryService dirService, long revision ) throws Exception 411 { 412 if ( dirService == null ) 413 { 414 return; 415 } 416 417 ChangeLog cl = dirService.getChangeLog(); 418 if ( cl.isEnabled() && ( revision < cl.getCurrentRevision() ) ) 419 { 420 LOG.debug( "Revert revision {}", revision ); 421 dirService.revert( revision ); 422 } 423 } 424}