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