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