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 */ 020package org.apache.directory.shared.ldap.schemaextractor.impl; 021 022 023import java.io.File; 024import java.io.FileNotFoundException; 025import java.io.FileOutputStream; 026import java.io.FileWriter; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.InvalidObjectException; 030import java.net.URL; 031import java.util.Enumeration; 032import java.util.Map; 033import java.util.Stack; 034import java.util.UUID; 035import java.util.Map.Entry; 036import java.util.regex.Pattern; 037 038import org.apache.directory.shared.i18n.I18n; 039import org.apache.directory.shared.ldap.model.constants.SchemaConstants; 040import org.apache.directory.shared.ldap.model.exception.LdapException; 041import org.apache.directory.shared.ldap.model.ldif.LdapLdifException; 042import org.apache.directory.shared.ldap.model.ldif.LdifEntry; 043import org.apache.directory.shared.ldap.model.ldif.LdifReader; 044import org.apache.directory.shared.ldap.schemaextractor.SchemaLdifExtractor; 045import org.apache.directory.shared.ldap.schemaextractor.UniqueResourceException; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049 050/** 051 * Extracts LDIF files for the schema repository onto a destination directory. 052 * 053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 054 */ 055public class DefaultSchemaLdifExtractor implements SchemaLdifExtractor 056{ 057 058 /** The base path. */ 059 private static final String BASE_PATH = ""; 060 061 /** The schema sub-directory. */ 062 private static final String SCHEMA_SUBDIR = "schema"; 063 064 /** The logger. */ 065 private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaLdifExtractor.class ); 066 067 /** 068 * The pattern to extract the schema from LDIF files. 069 * java.util.regex.Pattern is immutable so only one instance is needed for all uses. 070 */ 071 private static final Pattern EXTRACT_PATTERN = Pattern.compile( ".*schema" + "[/\\Q\\\\E]" + "ou=schema.*\\.ldif" ); 072 073 /** The extracted flag. */ 074 private boolean extracted; 075 076 /** The output directory. */ 077 private File outputDirectory; 078 079 /** The schema directory. */ 080 private File schemaDirectory; 081 082 083 /** 084 * Creates an extractor which deposits files into the specified output 085 * directory. 086 * 087 * @param outputDirectory the directory where the schema root is extracted 088 */ 089 public DefaultSchemaLdifExtractor( File outputDirectory ) 090 { 091 LOG.debug( "BASE_PATH set to {}, outputDirectory set to {}", BASE_PATH, outputDirectory ); 092 this.outputDirectory = outputDirectory; 093 this.schemaDirectory = new File( outputDirectory, SCHEMA_SUBDIR ); 094 095 if ( !outputDirectory.exists() ) 096 { 097 LOG.debug( "Creating output directory: {}", outputDirectory ); 098 if ( !outputDirectory.mkdir() ) 099 { 100 LOG.error( "Failed to create outputDirectory: {}", outputDirectory ); 101 } 102 } 103 else 104 { 105 LOG.debug( "Output directory exists: no need to create." ); 106 } 107 108 if ( !schemaDirectory.exists() ) 109 { 110 LOG.info( "Schema directory '{}' does NOT exist: extracted state set to false.", schemaDirectory ); 111 extracted = false; 112 } 113 else 114 { 115 LOG.info( "Schema directory '{}' does exist: extracted state set to true.", schemaDirectory ); 116 extracted = true; 117 } 118 } 119 120 121 /** 122 * Gets whether or not schema folder has been created or not. 123 * 124 * @return true if schema folder has already been extracted. 125 */ 126 public boolean isExtracted() 127 { 128 return extracted; 129 } 130 131 132 /** 133 * Extracts the LDIF files from a Jar file or copies exploded LDIF resources. 134 * 135 * @param overwrite over write extracted structure if true, false otherwise 136 * @throws IOException if schema already extracted and on IO errors 137 */ 138 public void extractOrCopy( boolean overwrite ) throws IOException 139 { 140 if ( !outputDirectory.exists() && !outputDirectory.mkdirs() ) 141 { 142 throw new IOException( I18n.err( I18n.ERR_09001_DIRECTORY_CREATION_FAILED, outputDirectory 143 .getAbsolutePath() ) ); 144 } 145 146 File schemaDirectory = new File( outputDirectory, SCHEMA_SUBDIR ); 147 148 if ( !schemaDirectory.exists() ) 149 { 150 if ( !schemaDirectory.mkdirs() ) 151 { 152 throw new IOException( I18n.err( I18n.ERR_09001_DIRECTORY_CREATION_FAILED, schemaDirectory 153 .getAbsolutePath() ) ); 154 } 155 } 156 else if ( !overwrite ) 157 { 158 throw new IOException( I18n.err( I18n.ERR_08001, schemaDirectory.getAbsolutePath() ) ); 159 } 160 161 Map<String, Boolean> list = ResourceMap.getResources( EXTRACT_PATTERN ); 162 163 for ( Entry<String, Boolean> entry : list.entrySet() ) 164 { 165 if ( entry.getValue() ) 166 { 167 extractFromClassLoader( entry.getKey() ); 168 } 169 else 170 { 171 File resource = new File( entry.getKey() ); 172 copyFile( resource, getDestinationFile( resource ) ); 173 } 174 } 175 } 176 177 178 /** 179 * Extracts the LDIF files from a Jar file or copies exploded LDIF 180 * resources without overwriting the resources if the schema has 181 * already been extracted. 182 * 183 * @throws IOException if schema already extracted and on IO errors 184 */ 185 public void extractOrCopy() throws IOException 186 { 187 extractOrCopy( false ); 188 } 189 190 191 /** 192 * Copies a file line by line from the source file argument to the 193 * destination file argument. 194 * 195 * @param source the source file to copy 196 * @param destination the destination to copy the source to 197 * @throws IOException if there are IO errors or the source does not exist 198 */ 199 private void copyFile( File source, File destination ) throws IOException 200 { 201 LOG.debug( "copyFile(): source = {}, destination = {}", source, destination ); 202 203 if ( !destination.getParentFile().exists() && !destination.getParentFile().mkdirs() ) 204 { 205 throw new IOException( I18n.err( I18n.ERR_09001_DIRECTORY_CREATION_FAILED, destination.getParentFile() 206 .getAbsolutePath() ) ); 207 } 208 209 if ( !source.getParentFile().exists() ) 210 { 211 throw new FileNotFoundException( I18n.err( I18n.ERR_08002, source.getAbsolutePath() ) ); 212 } 213 214 FileWriter out = new FileWriter( destination ); 215 216 try 217 { 218 LdifReader ldifReader = new LdifReader( source ); 219 boolean first = true; 220 LdifEntry ldifEntry = null; 221 222 while ( ldifReader.hasNext() ) 223 { 224 if ( first ) 225 { 226 ldifEntry = ldifReader.next(); 227 228 if ( ldifEntry.get( SchemaConstants.ENTRY_UUID_AT ) == null ) 229 { 230 // No UUID, let's create one 231 UUID entryUuid = UUID.randomUUID(); 232 ldifEntry.addAttribute( SchemaConstants.ENTRY_UUID_AT, entryUuid.toString() ); 233 } 234 235 first = false; 236 } 237 else 238 { 239 // throw an exception : we should not have more than one entry per schema ldif file 240 String msg = I18n.err( I18n.ERR_08003, source ); 241 LOG.error( msg ); 242 throw new InvalidObjectException( msg ); 243 } 244 } 245 246 ldifReader.close(); 247 248 // Add the version at the first line, to avoid a warning 249 String ldifString = "version: 1\n" + ldifEntry.toString(); 250 251 out.write( ldifString ); 252 out.flush(); 253 } 254 catch ( LdapLdifException ne ) 255 { 256 String msg = I18n.err( I18n.ERR_08004, source, ne.getLocalizedMessage() ); 257 LOG.error( msg ); 258 throw new InvalidObjectException( msg ); 259 } 260 catch ( LdapException ne ) 261 { 262 String msg = I18n.err( I18n.ERR_08004, source, ne.getLocalizedMessage() ); 263 LOG.error( msg ); 264 throw new InvalidObjectException( msg ); 265 } 266 finally 267 { 268 out.close(); 269 } 270 } 271 272 273 /** 274 * Assembles the destination file by appending file components previously 275 * pushed on the fileComponentStack argument. 276 * 277 * @param fileComponentStack stack containing pushed file components 278 * @return the assembled destination file 279 */ 280 private File assembleDestinationFile( Stack<String> fileComponentStack ) 281 { 282 File destinationFile = outputDirectory.getAbsoluteFile(); 283 284 while ( !fileComponentStack.isEmpty() ) 285 { 286 destinationFile = new File( destinationFile, fileComponentStack.pop() ); 287 } 288 289 return destinationFile; 290 } 291 292 293 /** 294 * Calculates the destination file. 295 * 296 * @param resource the source file 297 * @return the destination file's parent directory 298 */ 299 private File getDestinationFile( File resource ) 300 { 301 File parent = resource.getParentFile(); 302 Stack<String> fileComponentStack = new Stack<String>(); 303 fileComponentStack.push( resource.getName() ); 304 305 while ( parent != null ) 306 { 307 if ( parent.getName().equals( "schema" ) ) 308 { 309 // All LDIF files besides the schema.ldif are under the 310 // schema/schema base path. So we need to add one more 311 // schema component to all LDIF files minus this schema.ldif 312 fileComponentStack.push( "schema" ); 313 314 return assembleDestinationFile( fileComponentStack ); 315 } 316 317 fileComponentStack.push( parent.getName() ); 318 319 if ( parent.equals( parent.getParentFile() ) || parent.getParentFile() == null ) 320 { 321 throw new IllegalStateException( I18n.err( I18n.ERR_08005 ) ); 322 } 323 324 parent = parent.getParentFile(); 325 } 326 327 /* 328 329 this seems retarded so I replaced it for now with what is below it 330 will not break from loop above unless parent == null so the if is 331 never executed - just the else is executed every time 332 333 if ( parent != null ) 334 { 335 return assembleDestinationFile( fileComponentStack ); 336 } 337 else 338 { 339 throw new IllegalStateException( "parent cannot be null" ); 340 } 341 342 */ 343 344 throw new IllegalStateException( I18n.err( I18n.ERR_08006 ) ); 345 } 346 347 348 /** 349 * Gets the unique schema file resource from the class loader off the base path. If 350 * the same resource exists multiple times then an error will result since the resource 351 * is not unique. 352 * 353 * @param resourceName the file name of the resource to load 354 * @param resourceDescription human description of the resource 355 * @return the InputStream to read the contents of the resource 356 * @throws IOException if there are problems reading or finding a unique copy of the resource 357 */ 358 public static InputStream getUniqueResourceAsStream( String resourceName, String resourceDescription ) 359 throws IOException 360 { 361 resourceName = BASE_PATH + resourceName; 362 URL result = getUniqueResource( resourceName, resourceDescription ); 363 return result.openStream(); 364 } 365 366 367 /** 368 * Gets a unique resource from the class loader. 369 * 370 * @param resourceName the name of the resource 371 * @param resourceDescription the description of the resource 372 * @return the URL to the resource in the class loader 373 * @throws IOException if there is an IO error 374 */ 375 public static URL getUniqueResource( String resourceName, String resourceDescription ) throws IOException 376 { 377 Enumeration<URL> resources = DefaultSchemaLdifExtractor.class.getClassLoader().getResources( resourceName ); 378 if ( !resources.hasMoreElements() ) 379 { 380 throw new UniqueResourceException( resourceName, resourceDescription ); 381 } 382 URL result = resources.nextElement(); 383 if ( resources.hasMoreElements() ) 384 { 385 throw new UniqueResourceException( resourceName, result, resources, resourceDescription ); 386 } 387 return result; 388 } 389 390 391 /** 392 * Extracts the LDIF schema resource from class loader. 393 * 394 * @param resource the LDIF schema resource 395 * @throws IOException if there are IO errors 396 */ 397 private void extractFromClassLoader( String resource ) throws IOException 398 { 399 byte[] buf = new byte[512]; 400 InputStream in = DefaultSchemaLdifExtractor.getUniqueResourceAsStream( resource, 401 "LDIF file in schema repository" ); 402 403 try 404 { 405 File destination = new File( outputDirectory, resource ); 406 407 /* 408 * Do not overwrite an LDIF file if it has already been extracted. 409 */ 410 if ( destination.exists() ) 411 { 412 return; 413 } 414 415 if ( !destination.getParentFile().exists() && !destination.getParentFile().mkdirs() ) 416 { 417 throw new IOException( I18n.err( I18n.ERR_09001_DIRECTORY_CREATION_FAILED, destination 418 .getParentFile().getAbsolutePath() ) ); 419 } 420 421 FileOutputStream out = new FileOutputStream( destination ); 422 try 423 { 424 while ( in.available() > 0 ) 425 { 426 int readCount = in.read( buf ); 427 out.write( buf, 0, readCount ); 428 } 429 out.flush(); 430 } 431 finally 432 { 433 out.close(); 434 } 435 } 436 finally 437 { 438 in.close(); 439 } 440 } 441}