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