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.factory; 020 021 022import java.io.File; 023import java.io.FileNotFoundException; 024import java.io.InputStream; 025import java.lang.reflect.AnnotatedElement; 026import java.lang.reflect.Constructor; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030 031import org.apache.directory.api.ldap.model.entry.DefaultEntry; 032import org.apache.directory.api.ldap.model.exception.LdapException; 033import org.apache.directory.api.ldap.model.exception.LdapUnwillingToPerformException; 034import org.apache.directory.api.ldap.model.ldif.LdifEntry; 035import org.apache.directory.api.ldap.model.ldif.LdifReader; 036import org.apache.directory.api.ldap.model.name.DefaultDnFactory; 037import org.apache.directory.api.ldap.model.name.Dn; 038import org.apache.directory.api.ldap.model.schema.SchemaManager; 039import org.apache.directory.api.util.Network; 040import org.apache.directory.api.util.Strings; 041import org.apache.directory.server.core.annotations.AnnotationUtils; 042import org.apache.directory.server.core.annotations.ApplyLdifFiles; 043import org.apache.directory.server.core.annotations.ApplyLdifs; 044import org.apache.directory.server.core.annotations.ContextEntry; 045import org.apache.directory.server.core.annotations.CreateAuthenticator; 046import org.apache.directory.server.core.annotations.CreateDS; 047import org.apache.directory.server.core.annotations.CreateIndex; 048import org.apache.directory.server.core.annotations.CreatePartition; 049import org.apache.directory.server.core.annotations.LoadSchema; 050import org.apache.directory.server.core.api.DirectoryService; 051import org.apache.directory.server.core.api.DnFactory; 052import org.apache.directory.server.core.api.interceptor.Interceptor; 053import org.apache.directory.server.core.api.partition.Partition; 054import org.apache.directory.server.core.authn.AuthenticationInterceptor; 055import org.apache.directory.server.core.authn.Authenticator; 056import org.apache.directory.server.core.authn.DelegatingAuthenticator; 057import org.apache.directory.server.core.partition.impl.btree.AbstractBTreePartition; 058import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex; 059import org.apache.directory.server.core.partition.impl.btree.mavibot.MavibotIndex; 060import org.apache.directory.server.i18n.I18n; 061import org.junit.jupiter.api.extension.ExtensionContext; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064 065 066/** 067 * A Helper class used to create a DS from the annotations 068 * 069 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 070 */ 071public final class DSAnnotationProcessor 072{ 073 /** A logger for this class */ 074 private static final Logger LOG = LoggerFactory.getLogger( DSAnnotationProcessor.class ); 075 076 077 private DSAnnotationProcessor() 078 { 079 } 080 081 082 /** 083 * Create the DirectoryService 084 * 085 * @param dsBuilder The DirectoryService builder 086 * @return an instance of DirectoryService 087 * @throws Exception If the DirectoryService cannot be created 088 */ 089 public static DirectoryService createDS( CreateDS dsBuilder ) 090 throws Exception 091 { 092 if ( LOG.isDebugEnabled() ) 093 { 094 LOG.debug( "Starting DS {}...", dsBuilder.name() ); 095 } 096 097 Class<?> factory = dsBuilder.factory(); 098 DirectoryServiceFactory dsf = ( DirectoryServiceFactory ) factory.newInstance(); 099 100 DirectoryService service = dsf.getDirectoryService(); 101 service.setAccessControlEnabled( dsBuilder.enableAccessControl() ); 102 service.setAllowAnonymousAccess( dsBuilder.allowAnonAccess() ); 103 service.getChangeLog().setEnabled( dsBuilder.enableChangeLog() ); 104 105 dsf.init( dsBuilder.name() ); 106 107 for ( Class<?> interceptorClass : dsBuilder.additionalInterceptors() ) 108 { 109 service.addLast( ( Interceptor ) interceptorClass.newInstance() ); 110 } 111 112 List<Interceptor> interceptorList = service.getInterceptors(); 113 114 if ( dsBuilder.authenticators().length != 0 ) 115 { 116 AuthenticationInterceptor authenticationInterceptor = null; 117 118 for ( Interceptor interceptor : interceptorList ) 119 { 120 if ( interceptor instanceof AuthenticationInterceptor ) 121 { 122 authenticationInterceptor = ( AuthenticationInterceptor ) interceptor; 123 break; 124 } 125 } 126 127 if ( authenticationInterceptor == null ) 128 { 129 throw new IllegalStateException( 130 "authentication interceptor not found" ); 131 } 132 133 Set<Authenticator> authenticators = new HashSet<>(); 134 135 for ( CreateAuthenticator createAuthenticator : dsBuilder 136 .authenticators() ) 137 { 138 Authenticator auth = createAuthenticator.type().newInstance(); 139 140 if ( auth instanceof DelegatingAuthenticator ) 141 { 142 DelegatingAuthenticator dauth = ( DelegatingAuthenticator ) auth; 143 144 String host = createAuthenticator.delegateHost(); 145 146 if ( Strings.isEmpty( host ) ) 147 { 148 host = Network.LOOPBACK_HOSTNAME; 149 } 150 151 dauth.setDelegateHost( host ); 152 dauth.setDelegatePort( createAuthenticator.delegatePort() ); 153 dauth.setDelegateSsl( createAuthenticator.delegateSsl() ); 154 dauth.setDelegateTls( createAuthenticator.delegateTls() ); 155 dauth.setBaseDn( service.getDnFactory().create( createAuthenticator.baseDn() ) ); 156 dauth.setDelegateSslTrustManagerFQCN( createAuthenticator.delegateSslTrustManagerFQCN() ); 157 dauth.setDelegateTlsTrustManagerFQCN( createAuthenticator.delegateTlsTrustManagerFQCN() ); 158 } 159 160 authenticators.add( auth ); 161 } 162 163 authenticationInterceptor.setAuthenticators( authenticators ); 164 authenticationInterceptor.init( service ); 165 } 166 167 service.setInterceptors( interceptorList ); 168 169 SchemaManager schemaManager = service.getSchemaManager(); 170 171 // process the schemas 172 for ( LoadSchema loadedSchema : dsBuilder.loadedSchemas() ) 173 { 174 String schemaName = loadedSchema.name(); 175 Boolean enabled = loadedSchema.enabled(); 176 177 // Check if the schema is loaded or not 178 boolean isLoaded = schemaManager.isSchemaLoaded( schemaName ); 179 180 if ( !isLoaded ) 181 { 182 // We have to load the schema, if it exists 183 try 184 { 185 isLoaded = schemaManager.load( schemaName ); 186 } 187 catch ( LdapUnwillingToPerformException lutpe ) 188 { 189 // Cannot load the schema, it does not exists 190 LOG.error( lutpe.getMessage() ); 191 continue; 192 } 193 } 194 195 if ( isLoaded ) 196 { 197 if ( enabled ) 198 { 199 schemaManager.enable( schemaName ); 200 201 if ( schemaManager.isDisabled( schemaName ) ) 202 { 203 LOG.error( "Cannot enable {}", schemaName ); 204 } 205 } 206 else 207 { 208 schemaManager.disable( schemaName ); 209 210 if ( schemaManager.isEnabled( schemaName ) ) 211 { 212 LOG.error( "Cannot disable {}", schemaName ); 213 } 214 } 215 } 216 217 LOG.debug( "Loading schema {}, enabled= {}", schemaName, enabled ); 218 } 219 220 // Process the Partition, if any. 221 for ( CreatePartition createPartition : dsBuilder.partitions() ) 222 { 223 Partition partition; 224 225 // Determine the partition type 226 if ( createPartition.type() == Partition.class ) 227 { 228 // The annotation does not specify a specific partition type. 229 // We use the partition factory to create partition and index 230 // instances. 231 PartitionFactory partitionFactory = dsf.getPartitionFactory(); 232 partition = partitionFactory.createPartition( 233 schemaManager, 234 service.getDnFactory(), 235 createPartition.name(), 236 createPartition.suffix(), 237 createPartition.cacheSize(), 238 new File( service.getInstanceLayout().getPartitionsDirectory(), createPartition.name() ) ); 239 240 CreateIndex[] indexes = createPartition.indexes(); 241 242 for ( CreateIndex createIndex : indexes ) 243 { 244 partitionFactory.addIndex( partition, 245 createIndex.attribute(), createIndex.cacheSize() ); 246 } 247 248 partition.initialize(); 249 } 250 else 251 { 252 // The annotation contains a specific partition type, we use 253 // that type. 254 Class<?>[] partypes = new Class[] 255 { SchemaManager.class, DnFactory.class }; 256 Constructor<?> constructor = createPartition.type().getConstructor( partypes ); 257 partition = ( Partition ) constructor.newInstance( schemaManager, service.getDnFactory() ); 258 partition.setId( createPartition.name() ); 259 partition.setSuffixDn( new Dn( schemaManager, createPartition.suffix() ) ); 260 261 if ( partition instanceof AbstractBTreePartition ) 262 { 263 AbstractBTreePartition btreePartition = ( AbstractBTreePartition ) partition; 264 btreePartition.setCacheSize( createPartition.cacheSize() ); 265 btreePartition.setPartitionPath( new File( service 266 .getInstanceLayout().getPartitionsDirectory(), 267 createPartition.name() ).toURI() ); 268 269 // Process the indexes if any 270 CreateIndex[] indexes = createPartition.indexes(); 271 272 for ( CreateIndex createIndex : indexes ) 273 { 274 if ( createIndex.type() == MavibotIndex.class ) 275 { 276 // Mavibot index 277 MavibotIndex index = new MavibotIndex( createIndex.attribute(), false ); 278 279 btreePartition.addIndexedAttributes( index ); 280 } 281 else 282 { 283 // The annotation does not specify a specific index 284 // type. 285 // We use the generic index implementation. 286 JdbmIndex index = new JdbmIndex( createIndex.attribute(), false ); 287 288 btreePartition.addIndexedAttributes( index ); 289 } 290 } 291 } 292 } 293 294 partition.setSchemaManager( schemaManager ); 295 296 // Inject the partition into the DirectoryService 297 service.addPartition( partition ); 298 299 // Last, process the context entry 300 ContextEntry contextEntry = createPartition.contextEntry(); 301 302 if ( contextEntry != null ) 303 { 304 injectEntries( service, contextEntry.entryLdif() ); 305 } 306 } 307 308 // Inject the DnFactory in the DLAP codec service 309 service.getLdapCodecService().setDnfactory( new DefaultDnFactory( service.getSchemaManager(), 1000 ) ); 310 311 return service; 312 } 313 314 315 /** 316 * Create a DirectoryService from a Unit test annotation 317 * 318 * @param description The annotations containing the info from which we will create 319 * the DS 320 * @return A valid DirectoryService 321 * @throws Exception If the DirectoryService instance can't be returned 322 */ 323 public static DirectoryService getDirectoryService( CreateDS dsBuilder ) 324 throws Exception 325 { 326 if ( dsBuilder != null ) 327 { 328 return createDS( dsBuilder ); 329 } 330 else 331 { 332 LOG.debug( "No CreateDS annotation." ); 333 return null; 334 } 335 } 336 337 338 /** 339 * Create a DirectoryService from an annotation. The @CreateDS annotation 340 * must be associated with either the method or the encapsulating class. We 341 * will first try to get the annotation from the method, and if there is 342 * none, then we try at the class level. 343 * 344 * @return A valid DS 345 * @throws Exception If the DirectoryService instance can't be returned 346 */ 347 public static DirectoryService getDirectoryService() throws Exception 348 { 349 Object instance = AnnotationUtils.getInstance( CreateDS.class ); 350 CreateDS dsBuilder = null; 351 352 if ( instance != null ) 353 { 354 dsBuilder = ( CreateDS ) instance; 355 356 // Ok, we have found a CreateDS annotation. Process it now. 357 return createDS( dsBuilder ); 358 } 359 360 throw new LdapException( I18n.err( I18n.ERR_114 ) ); 361 } 362 363 364 /** 365 * injects an LDIF entry in the given DirectoryService 366 * 367 * @param entry the LdifEntry to be injected 368 * @param service the DirectoryService 369 * @throws Exception If the entry cannot be injected 370 */ 371 private static void injectEntry( LdifEntry entry, DirectoryService service ) 372 throws LdapException 373 { 374 if ( entry.isChangeAdd() || entry.isLdifContent() ) 375 { 376 service.getAdminSession().add( 377 new DefaultEntry( service.getSchemaManager(), entry 378 .getEntry() ) ); 379 } 380 else if ( entry.isChangeModify() ) 381 { 382 service.getAdminSession().modify( entry.getDn(), 383 entry.getModifications() ); 384 } 385 else 386 { 387 String message = I18n.err( I18n.ERR_117, entry.getChangeType() ); 388 throw new LdapException( message ); 389 } 390 } 391 392 393 /** 394 * injects the LDIF entries present in a LDIF file 395 * 396 * @param clazz The class which classLoaded will be use to retrieve the resources 397 * @param service the DirectoryService 398 * @param ldifFiles array of LDIF file names (only ) 399 * @throws Exception If we weren't able to inject LdifFiles 400 */ 401 public static void injectLdifFiles( Class<?> clazz, 402 DirectoryService service, String[] ldifFiles ) throws Exception 403 { 404 if ( ( ldifFiles != null ) && ( ldifFiles.length > 0 ) ) 405 { 406 for ( String ldifFile : ldifFiles ) 407 { 408 InputStream is = clazz.getClassLoader().getResourceAsStream( 409 ldifFile ); 410 if ( is == null ) 411 { 412 throw new FileNotFoundException( "LDIF file '" + ldifFile 413 + "' not found." ); 414 } 415 else 416 { 417 LdifReader ldifReader = new LdifReader( is ); 418 419 for ( LdifEntry entry : ldifReader ) 420 { 421 injectEntry( entry, service ); 422 } 423 424 ldifReader.close(); 425 } 426 } 427 } 428 } 429 430 431 /** 432 * Inject an ldif String into the server. Dn must be relative to the root. 433 * 434 * @param service the directory service to use 435 * @param ldif the ldif containing entries to add to the server. 436 * @throws Exception if there is a problem adding the entries from the LDIF 437 */ 438 public static void injectEntries( DirectoryService service, String ldif ) 439 throws Exception 440 { 441 try ( LdifReader reader = new LdifReader() ) 442 { 443 List<LdifEntry> entries = reader.parseLdif( ldif ); 444 445 for ( LdifEntry entry : entries ) 446 { 447 injectEntry( entry, service ); 448 } 449 } 450 } 451 452 453 /** 454 * Load the schemas, and enable/disable them. 455 * 456 * @param context The text context 457 * @param service The DirectoryService instance 458 */ 459 public static void loadSchemas( ExtensionContext context, DirectoryService service ) 460 { 461 if ( context == null ) 462 { 463 return; 464 } 465 466 AnnotatedElement annotations = context.getTestClass().get(); 467 468 LoadSchema loadSchema = annotations.getDeclaredAnnotation( LoadSchema.class ); 469 470 if ( loadSchema != null ) 471 { 472 System.out.println( loadSchema ); 473 } 474 } 475 476 477 private static boolean isDn( String str ) 478 { 479 if ( ( Strings.isEmpty( str ) ) || ( str.length() < 3 ) ) 480 { 481 return false; 482 } 483 484 return ( ( ( str.charAt( 0 ) == 'd' ) || ( str.charAt( 0 ) == 'D' ) ) 485 && ( ( str.charAt( 1 ) == 'n' ) || ( str.charAt( 1 ) == 'N' ) ) 486 && ( str.charAt( 2 ) == ':' ) ); 487 } 488 489 490 /** 491 * Apply the LDIF entries to the given service 492 * 493 * @param annotations The test annotations 494 * @param displayName The context in which this methods is executed (suite name, class name or method name) 495 * @param service The DirectoryService instance 496 * @throws Exception If we can't apply the ldifs 497 */ 498 public static void applyLdifs( AnnotatedElement annotations, String displayName, DirectoryService service ) 499 throws Exception 500 { 501 if ( annotations == null ) 502 { 503 return; 504 } 505 506 ApplyLdifFiles applyLdifFiles = annotations.getDeclaredAnnotation( ApplyLdifFiles.class ); 507 508 if ( applyLdifFiles != null ) 509 { 510 LOG.debug( "Applying {} to {}", applyLdifFiles.value(), displayName ); 511 injectLdifFiles( applyLdifFiles.clazz(), service, applyLdifFiles.value() ); 512 } 513 514 ApplyLdifs applyLdifs = annotations.getDeclaredAnnotation( ApplyLdifs.class ); 515 516 if ( ( applyLdifs != null ) && ( applyLdifs.value() != null ) ) 517 { 518 String[] ldifs = applyLdifs.value(); 519 520 StringBuilder sb = new StringBuilder(); 521 522 for ( int i = 0; i < ldifs.length; ) 523 { 524 String str = ldifs[i++].trim(); 525 526 if ( isDn( str ) ) 527 { 528 sb.append( str ).append( '\n' ); 529 530 // read the rest of lines till we encounter Dn again 531 while ( i < ldifs.length ) 532 { 533 str = ldifs[i++]; 534 535 if ( !isDn( str ) ) 536 { 537 sb.append( str ).append( '\n' ); 538 } 539 else 540 { 541 break; 542 } 543 } 544 545 LOG.debug( "Applying {} to {}", sb, displayName ); 546 injectEntries( service, sb.toString() ); 547 sb.setLength( 0 ); 548 549 i--; // step up a line 550 } 551 } 552 } 553 } 554}