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    
021    package org.apache.directory.server.config;
022    
023    
024    import java.io.BufferedReader;
025    import java.io.File;
026    import java.io.FileNotFoundException;
027    import java.io.FileOutputStream;
028    import java.io.FileReader;
029    import java.io.FileWriter;
030    import java.io.IOException;
031    import java.io.InputStream;
032    import java.util.Map;
033    import java.util.Stack;
034    import java.util.Map.Entry;
035    import java.util.regex.Pattern;
036    
037    import org.apache.directory.server.i18n.I18n;
038    import org.apache.directory.shared.ldap.schema.ldif.extractor.impl.DefaultSchemaLdifExtractor;
039    import org.apache.directory.shared.ldap.schema.ldif.extractor.impl.ResourceMap;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    
044    /**
045     * A class to copy the default config to the work directory of a DirectoryService instance.
046     * 
047     * NOTE: much of this class code is duplicated from DefaultSchemaLdifExtractor class
048     *       We should create a AbstractLdifExtractor class and move the reusable code there 
049     *  
050     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
051     * @version $Rev$, $Date$
052     */
053    public class LdifConfigExtractor
054    {
055        
056        private static final String CONFIG_SUBDIR = "config";
057        
058        private static final Logger LOG = LoggerFactory.getLogger( LdifConfigExtractor.class );
059        
060        
061        /**
062         * Extracts the LDIF files from a Jar file or copies exploded LDIF resources.
063         *
064         * @param overwrite over write extracted structure if true, false otherwise
065         * @throws IOException if schema already extracted and on IO errors
066         */
067        public static void extract( File outputDirectory, boolean overwrite ) throws IOException
068        {
069            if ( !outputDirectory.exists() )
070            {
071                LOG.debug( "creating non existing output directory {}", outputDirectory.getAbsolutePath() );
072                outputDirectory.mkdir();
073            }
074            
075            File configDirectory = new File( outputDirectory, CONFIG_SUBDIR );
076    
077            if ( !configDirectory.exists() )
078            {
079                LOG.debug( "creating non existing config directory {}", configDirectory.getAbsolutePath() );
080                configDirectory.mkdir();
081            }
082            else if ( !overwrite )
083            {
084                throw new IOException( I18n.err( I18n.ERR_508, configDirectory.getAbsolutePath() ) );
085            }
086    
087            LOG.info( "extracting the configuration to the directory at {}", configDirectory.getAbsolutePath() );
088    
089            Pattern pattern = Pattern.compile( ".*config/ou=config.*\\.ldif" );
090            Map<String, Boolean> list = ResourceMap.getResources( pattern );
091    
092            System.out.println( list );
093            
094            for ( Entry<String, Boolean> entry : list.entrySet() )
095            {
096                if ( entry.getValue() )
097                {
098                    extractFromJar( outputDirectory, entry.getKey() );
099                }
100                else
101                {
102                    File resource = new File( entry.getKey() );
103                    copyFile( resource, getDestinationFile( outputDirectory, resource ) );
104                }
105            }
106        }
107    
108        
109        /**
110         * Copies a file line by line from the source file argument to the 
111         * destination file argument.
112         *
113         * @param source the source file to copy
114         * @param destination the destination to copy the source to
115         * @throws IOException if there are IO errors or the source does not exist
116         */
117        private static void copyFile( File source, File destination ) throws IOException
118        {
119            LOG.debug( "copyFile(): source = {}, destination = {}", source, destination );
120            
121            if ( ! destination.getParentFile().exists() )
122            {
123                destination.getParentFile().mkdirs();
124            }
125            
126            if ( ! source.getParentFile().exists() )
127            {
128                throw new FileNotFoundException( I18n.err( I18n.ERR_509, source.getAbsolutePath() ) );
129            }
130            
131            FileWriter out = new FileWriter( destination );
132            BufferedReader in = new BufferedReader( new FileReader( source ) );
133            String line;
134            while ( null != ( line = in.readLine() ) )
135            {
136                out.write( line + "\n" ); 
137            }
138            
139            in.close();
140            out.flush();
141            out.close();
142        }
143    
144        /**
145         * Extracts the LDIF schema resource from a Jar.
146         *
147         * @param resource the LDIF schema resource
148         * @throws IOException if there are IO errors
149         */
150        private static void extractFromJar( File outputDirectory, String resource ) throws IOException
151        {
152            byte[] buf = new byte[512];
153            InputStream in = DefaultSchemaLdifExtractor.getUniqueResourceAsStream( resource,
154                "LDIF file in config repository" );
155    
156            try
157            {
158                File destination = new File( outputDirectory, resource );
159    
160                /*
161                 * Do not overwrite an LDIF file if it has already been extracted.
162                 */
163                if ( destination.exists() )
164                {
165                    return;
166                }
167                
168                if ( ! destination.getParentFile().exists() )
169                {
170                    destination.getParentFile().mkdirs();
171                }
172                
173                FileOutputStream out = new FileOutputStream( destination );
174                try
175                {
176                    while ( in.available() > 0 )
177                    {
178                        int readCount = in.read( buf );
179                        out.write( buf, 0, readCount );
180                    }
181                    out.flush();
182                } 
183                finally
184                {
185                    out.close();
186                }
187            }
188            finally
189            {
190                in.close();
191            }
192        }
193    
194        
195        /**
196         * Calculates the destination file.
197         *
198         * @param resource the source file
199         * @return the destination file's parent directory
200         */
201        private static File getDestinationFile( File outputDirectory, File resource )
202        {
203            File parent = resource.getParentFile();
204            Stack<String> fileComponentStack = new Stack<String>();
205            fileComponentStack.push( resource.getName() );
206            
207            while ( parent != null )
208            {
209                if ( parent.getName().equals( "config" ) )
210                {
211                    // All LDIF files besides the config.ldif are under the 
212                    // config/config base path. So we need to add one more 
213                    // schema component to all LDIF files minus this config.ldif
214                    fileComponentStack.push( "config" );
215                    
216                    return assembleDestinationFile( outputDirectory, fileComponentStack );
217                }
218    
219                fileComponentStack.push( parent.getName() );
220                
221                if ( parent.equals( parent.getParentFile() )
222                        || parent.getParentFile() == null )
223                {
224                    throw new IllegalStateException( I18n.err( I18n.ERR_510 ) );
225                }
226                
227                parent = parent.getParentFile();
228            }
229    
230            throw new IllegalStateException( I18n.err( I18n.ERR_511 ) );
231        }
232    
233        /**
234         * Assembles the destination file by appending file components previously
235         * pushed on the fileComponentStack argument.
236         *
237         * @param fileComponentStack stack containing pushed file components
238         * @return the assembled destination file
239         */
240        private static File assembleDestinationFile( File outputDirectory, Stack<String> fileComponentStack )
241        {
242            File destinationFile = outputDirectory.getAbsoluteFile();
243            
244            while ( ! fileComponentStack.isEmpty() )
245            {
246                destinationFile = new File( destinationFile, fileComponentStack.pop() );
247            }
248            
249            return destinationFile;
250        }
251    
252    }