View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.api.ldap.schemaextractor.impl;
21  
22  
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.InvalidObjectException;
30  import java.net.URL;
31  import java.util.Enumeration;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  import java.util.Stack;
35  import java.util.UUID;
36  import java.util.regex.Pattern;
37  
38  import org.apache.directory.api.i18n.I18n;
39  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
40  import org.apache.directory.api.ldap.model.exception.LdapException;
41  import org.apache.directory.api.ldap.model.ldif.LdapLdifException;
42  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
43  import org.apache.directory.api.ldap.model.ldif.LdifReader;
44  import org.apache.directory.api.ldap.schemaextractor.SchemaLdifExtractor;
45  import org.apache.directory.api.ldap.schemaextractor.UniqueResourceException;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  
50  /**
51   * Extracts LDIF files for the schema repository onto a destination directory.
52   *
53   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
54   */
55  public class DefaultSchemaLdifExtractor implements SchemaLdifExtractor
56  {
57      /** The base path. */
58      private static final String BASE_PATH = "";
59  
60      /** The schema sub-directory. */
61      private static final String SCHEMA_SUBDIR = "schema";
62  
63      /** The logger. */
64      private static final Logger LOG = LoggerFactory.getLogger( DefaultSchemaLdifExtractor.class );
65  
66      /**
67       * The pattern to extract the schema from LDIF files.
68       * java.util.regex.Pattern is immutable so only one instance is needed for all uses.
69       */
70      private static final Pattern EXTRACT_PATTERN = Pattern.compile( ".*schema" + "[/\\Q\\\\E]" + "ou=schema.*\\.ldif" );
71  
72      /** The extracted flag. */
73      private boolean extracted;
74  
75      /** The output directory. */
76      private File outputDirectory;
77  
78  
79      /**
80       * Creates an extractor which deposits files into the specified output
81       * directory.
82       *
83       * @param outputDirectory the directory where the schema root is extracted
84       */
85      public DefaultSchemaLdifExtractor( File outputDirectory )
86      {
87          LOG.debug( "BASE_PATH set to {}, outputDirectory set to {}", BASE_PATH, outputDirectory );
88          this.outputDirectory = outputDirectory;
89          File schemaDirectory = new File( outputDirectory, SCHEMA_SUBDIR );
90  
91          if ( !outputDirectory.exists() )
92          {
93              LOG.debug( "Creating output directory: {}", outputDirectory );
94              if ( !outputDirectory.mkdir() )
95              {
96                  LOG.error( "Failed to create outputDirectory: {}", outputDirectory );
97              }
98          }
99          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 }