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.factory; 020 021 022 import java.io.File; 023 import java.io.FileNotFoundException; 024 import java.io.InputStream; 025 import java.lang.reflect.Method; 026 import java.util.List; 027 028 import javax.naming.NamingException; 029 030 import org.apache.directory.server.core.DirectoryService; 031 import org.apache.directory.server.core.annotations.ApplyLdifFiles; 032 import org.apache.directory.server.core.annotations.ApplyLdifs; 033 import org.apache.directory.server.core.annotations.ContextEntry; 034 import org.apache.directory.server.core.annotations.CreateDS; 035 import org.apache.directory.server.core.annotations.CreateIndex; 036 import org.apache.directory.server.core.annotations.CreatePartition; 037 import org.apache.directory.server.core.interceptor.Interceptor; 038 import org.apache.directory.server.core.partition.Partition; 039 import org.apache.directory.server.core.partition.impl.btree.BTreePartition; 040 import org.apache.directory.server.i18n.I18n; 041 import org.apache.directory.server.xdbm.GenericIndex; 042 import org.apache.directory.server.xdbm.Index; 043 import org.apache.directory.shared.ldap.entry.DefaultServerEntry; 044 import org.apache.directory.shared.ldap.ldif.LdifEntry; 045 import org.apache.directory.shared.ldap.ldif.LdifReader; 046 import org.junit.runner.Description; 047 import org.slf4j.Logger; 048 import org.slf4j.LoggerFactory; 049 050 051 /** 052 * A Helper class used to create a DS from the annotations 053 * 054 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 055 * @version $Rev$, $Date$ 056 */ 057 public class DSAnnotationProcessor 058 { 059 /** A logger for this class */ 060 private static final Logger LOG = LoggerFactory.getLogger( DSAnnotationProcessor.class ); 061 062 063 /** 064 * Create the DirectoryService 065 */ 066 private static DirectoryService createDS( CreateDS dsBuilder ) throws Exception 067 { 068 LOG.debug( "Starting DS {}...", dsBuilder.name() ); 069 Class<?> factory = dsBuilder.factory(); 070 DirectoryServiceFactory dsf = ( DirectoryServiceFactory ) factory.newInstance(); 071 072 DirectoryService service = dsf.getDirectoryService(); 073 service.setAccessControlEnabled( dsBuilder.enableAccessControl() ); 074 service.setAllowAnonymousAccess( dsBuilder.allowAnonAccess() ); 075 service.getChangeLog().setEnabled( dsBuilder.enableChangeLog() ); 076 077 List<Interceptor> interceptorList = service.getInterceptors(); 078 for ( Class<?> interceptorClass : dsBuilder.additionalInterceptors() ) 079 { 080 interceptorList.add( ( Interceptor ) interceptorClass.newInstance() ); 081 } 082 083 service.setInterceptors( interceptorList ); 084 085 dsf.init( dsBuilder.name() ); 086 087 // Process the Partition, if any. 088 for ( CreatePartition createPartition : dsBuilder.partitions() ) 089 { 090 Partition partition; 091 092 // Determine the partition type 093 if ( createPartition.type() == Partition.class ) 094 { 095 // The annotation does not specify a specific partition type. 096 // We use the partition factory to create partition and index instances. 097 PartitionFactory partitionFactory = dsf.getPartitionFactory(); 098 partition = partitionFactory.createPartition( createPartition.name(), createPartition.suffix(), 099 createPartition.cacheSize(), new File( service.getWorkingDirectory(), createPartition.name() ) ); 100 101 CreateIndex[] indexes = createPartition.indexes(); 102 for ( CreateIndex createIndex : indexes ) 103 { 104 partitionFactory.addIndex( partition, createIndex.attribute(), createIndex.cacheSize() ); 105 } 106 } 107 else 108 { 109 // The annotation contains a specific partition type, we use that type. 110 partition = createPartition.type().newInstance(); 111 partition.setId( createPartition.name() ); 112 partition.setSuffix( createPartition.suffix() ); 113 114 if ( partition instanceof BTreePartition<?> ) 115 { 116 BTreePartition<?> btreePartition = ( BTreePartition<?> ) partition; 117 btreePartition.setCacheSize( createPartition.cacheSize() ); 118 btreePartition.setPartitionDir( new File( service.getWorkingDirectory(), createPartition.name() ) ); 119 120 // Process the indexes if any 121 CreateIndex[] indexes = createPartition.indexes(); 122 123 for ( CreateIndex createIndex : indexes ) 124 { 125 Index index; 126 if ( createIndex.type() == Index.class ) 127 { 128 // The annotation does not specify a specific index type. 129 // We use the generic index implementation. 130 index = new GenericIndex( createIndex.attribute(), createIndex.cacheSize() ); 131 } 132 else 133 { 134 // The annotation contains a specific index type, we use that type. 135 index = createIndex.type().newInstance(); 136 index.setAttributeId( createIndex.attribute() ); 137 index.setCacheSize( createIndex.cacheSize() ); 138 } 139 btreePartition.addIndexedAttributes( index ); 140 } 141 } 142 } 143 144 partition.setSchemaManager( service.getSchemaManager() ); 145 146 // Inject the partition into the DirectoryService 147 service.addPartition( partition ); 148 149 // Last, process the context entry 150 ContextEntry contextEntry = createPartition.contextEntry(); 151 152 if ( contextEntry != null ) 153 { 154 injectEntries( service, contextEntry.entryLdif() ); 155 } 156 } 157 158 return service; 159 } 160 161 162 /** 163 * Create a DirectoryService from a Unit test annotation 164 * 165 * @param description The annotations containing the info from which we will create the DS 166 * @return A valid DS 167 */ 168 public static DirectoryService getDirectoryService( Description description ) throws Exception 169 { 170 CreateDS dsBuilder = description.getAnnotation( CreateDS.class ); 171 172 if ( dsBuilder != null ) 173 { 174 return createDS( dsBuilder ); 175 } 176 else 177 { 178 LOG.debug( "No {} DS.", description.getDisplayName() ); 179 return null; 180 } 181 } 182 183 184 /** 185 * Create a DirectoryService from an annotation. The @CreateDS annotation must 186 * be associated with either the method or the encapsulating class. We will first 187 * try to get the annotation from the method, and if there is none, then we try 188 * at the class level. 189 * 190 * @return A valid DS 191 */ 192 public static DirectoryService getDirectoryService() throws Exception 193 { 194 CreateDS dsBuilder = null; 195 196 // Get the caller by inspecting the stackTrace 197 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 198 199 // In Java5 the 0th stacktrace element is: java.lang.Thread.dumpThreads(Native Method) 200 int index = stackTrace[0].getMethodName().equals( "dumpThreads" ) ? 3 : 2; 201 202 // Get the enclosing class 203 Class<?> classCaller = Class.forName( stackTrace[index].getClassName() ); 204 205 // Get the current method 206 String methodCaller = stackTrace[index].getMethodName(); 207 208 // Check if we have any annotation associated with the method 209 Method[] methods = classCaller.getMethods(); 210 211 for ( Method method : methods ) 212 { 213 if ( methodCaller.equals( method.getName() ) ) 214 { 215 dsBuilder = method.getAnnotation( CreateDS.class ); 216 217 if ( dsBuilder != null ) 218 { 219 break; 220 } 221 } 222 } 223 224 // No : look at the class level 225 if ( dsBuilder == null ) 226 { 227 dsBuilder = classCaller.getAnnotation( CreateDS.class ); 228 } 229 230 // Ok, we have found a CreateDS annotation. Process it now. 231 return createDS( dsBuilder ); 232 } 233 234 235 /** 236 * injects an LDIF entry in the given DirectoryService 237 * 238 * @param entry the LdifEntry to be injected 239 * @param service the DirectoryService 240 * @throws Exception 241 */ 242 private static void injectEntry( LdifEntry entry, DirectoryService service ) throws Exception 243 { 244 if ( entry.isChangeAdd() ) 245 { 246 service.getAdminSession().add( new DefaultServerEntry( service.getSchemaManager(), entry.getEntry() ) ); 247 } 248 else if ( entry.isChangeModify() ) 249 { 250 service.getAdminSession().modify( entry.getDn(), entry.getModificationItems() ); 251 } 252 else 253 { 254 String message = I18n.err( I18n.ERR_117, entry.getChangeType() ); 255 throw new NamingException( message ); 256 } 257 } 258 259 260 /** 261 * injects the LDIF entries present in a LDIF file 262 * 263 * @param service the DirectoryService 264 * @param ldifFiles the array of LDIF file names (only ) 265 * @throws Exception 266 */ 267 public static void injectLdifFiles( Class<?> clazz, DirectoryService service, String[] ldifFiles ) throws Exception 268 { 269 if ( ( ldifFiles != null ) && ( ldifFiles.length > 0 ) ) 270 { 271 for ( String ldifFile : ldifFiles ) 272 { 273 InputStream is = clazz.getClassLoader().getResourceAsStream( ldifFile ); 274 if ( is == null ) 275 { 276 throw new FileNotFoundException( "LDIF file '" + ldifFile + "' not found." ); 277 } 278 else 279 { 280 try 281 { 282 LdifReader ldifReader = new LdifReader( is ); 283 284 for ( LdifEntry entry : ldifReader ) 285 { 286 injectEntry( entry, service ); 287 } 288 289 ldifReader.close(); 290 } 291 catch ( Exception e ) 292 { 293 LOG.error( I18n.err( I18n.ERR_80, ldifFile, e.getLocalizedMessage() ) ); 294 } 295 } 296 } 297 } 298 } 299 300 301 /** 302 * Inject an ldif String into the server. DN must be relative to the 303 * root. 304 * 305 * @param service the directory service to use 306 * @param ldif the ldif containing entries to add to the server. 307 * @throws NamingException if there is a problem adding the entries from the LDIF 308 */ 309 public static void injectEntries( DirectoryService service, String ldif ) throws Exception 310 { 311 LdifReader reader = new LdifReader(); 312 List<LdifEntry> entries = reader.parseLdif( ldif ); 313 314 for ( LdifEntry entry : entries ) 315 { 316 injectEntry( entry, service ); 317 } 318 319 // And close the reader 320 reader.close(); 321 } 322 323 324 /** 325 * Apply the LDIF entries to the given service 326 */ 327 public static void applyLdifs( Description desc, DirectoryService service ) throws Exception 328 { 329 if ( desc == null ) 330 { 331 return; 332 } 333 334 ApplyLdifFiles applyLdifFiles = desc.getAnnotation( ApplyLdifFiles.class ); 335 336 if ( applyLdifFiles != null ) 337 { 338 LOG.debug( "Applying {} to {}", applyLdifFiles.value(), desc.getDisplayName() ); 339 injectLdifFiles( desc.getClass(), service, applyLdifFiles.value() ); 340 } 341 342 ApplyLdifs applyLdifs = desc.getAnnotation( ApplyLdifs.class ); 343 344 if ( ( applyLdifs != null ) && ( applyLdifs.value() != null ) ) 345 { 346 String[] ldifs = applyLdifs.value(); 347 348 String DN_START = "dn:"; 349 350 StringBuilder sb = new StringBuilder(); 351 352 for ( int i = 0; i < ldifs.length; ) 353 { 354 String s = ldifs[i++].trim(); 355 if ( s.startsWith( DN_START ) ) 356 { 357 sb.append( s ).append( '\n' ); 358 359 // read the rest of lines till we encounter DN again 360 while ( i < ldifs.length ) 361 { 362 s = ldifs[i++]; 363 if ( !s.startsWith( DN_START ) ) 364 { 365 sb.append( s ).append( '\n' ); 366 } 367 else 368 { 369 break; 370 } 371 } 372 373 LOG.debug( "Applying {} to {}", sb, desc.getDisplayName() ); 374 injectEntries( service, sb.toString() ); 375 sb.setLength( 0 ); 376 377 i--; // step up a line 378 } 379 } 380 } 381 } 382 }