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 021package org.apache.directory.server.config; 022 023 024import java.io.BufferedReader; 025import java.io.File; 026import java.io.FileNotFoundException; 027import java.io.IOException; 028import java.io.InputStream; 029import java.io.OutputStream; 030import java.io.Writer; 031import java.net.URL; 032import java.nio.charset.StandardCharsets; 033import java.nio.file.Files; 034import java.util.Map; 035import java.util.Map.Entry; 036import java.util.Stack; 037import java.util.regex.Pattern; 038 039import org.apache.directory.api.ldap.schema.extractor.impl.DefaultSchemaLdifExtractor; 040import org.apache.directory.api.ldap.schema.extractor.impl.ResourceMap; 041import org.apache.directory.api.util.Strings; 042import org.apache.directory.server.i18n.I18n; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046 047/** 048 * A class to copy the default config to the work directory of a DirectoryService instance. 049 * 050 * NOTE: much of this class code is duplicated from DefaultSchemaLdifExtractor class 051 * We should create a AbstractLdifExtractor class and move the reusable code there 052 * 053 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 054 */ 055public final class LdifConfigExtractor 056{ 057 058 public static final String LDIF_CONFIG_FILE = "config.ldif"; 059 060 private static final String CONFIG_SUBDIR = "config"; 061 062 private static final Logger LOG = LoggerFactory.getLogger( LdifConfigExtractor.class ); 063 064 // java.util.regex.Pattern is immutable so only one instance is needed for all uses. 065 private static final Pattern EXTRACT_PATTERN = Pattern.compile( ".*config" 066 + "[/\\Q\\\\E]" + "ou=config.*\\.ldif" ); 067 068 069 private LdifConfigExtractor() 070 { 071 } 072 073 074 /** 075 * Extracts the LDIF files from a Jar file or copies exploded LDIF resources. 076 * 077 * @param outputDirectory The directory where to extract the configuration 078 * @param overwrite over write extracted structure if true, false otherwise 079 * @throws IOException if schema already extracted and on IO errors 080 */ 081 public static void extract( File outputDirectory, boolean overwrite ) throws IOException 082 { 083 if ( !outputDirectory.exists() ) 084 { 085 LOG.debug( "creating non existing output directory {}", outputDirectory.getAbsolutePath() ); 086 if ( !outputDirectory.mkdir() ) 087 { 088 throw new IOException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, outputDirectory ) ); 089 } 090 } 091 092 File configDirectory = new File( outputDirectory, CONFIG_SUBDIR ); 093 094 if ( !configDirectory.exists() ) 095 { 096 LOG.debug( "creating non existing config directory {}", configDirectory.getAbsolutePath() ); 097 if ( !configDirectory.mkdir() ) 098 { 099 throw new IOException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, configDirectory ) ); 100 } 101 } 102 else if ( !overwrite ) 103 { 104 throw new IOException( I18n.err( I18n.ERR_508, configDirectory.getAbsolutePath() ) ); 105 } 106 107 LOG.debug( "extracting the configuration to the directory at {}", configDirectory.getAbsolutePath() ); 108 109 Map<String, Boolean> list = ResourceMap.getResources( EXTRACT_PATTERN ); 110 111 for ( Entry<String, Boolean> entry : list.entrySet() ) 112 { 113 if ( entry.getValue() ) 114 { 115 extractFromJar( outputDirectory, entry.getKey() ); 116 } 117 else 118 { 119 File resource = new File( entry.getKey() ); 120 copyFile( resource, getDestinationFile( outputDirectory, resource ) ); 121 } 122 } 123 } 124 125 126 /** 127 * Copies a file line by line from the source file argument to the 128 * destination file argument. 129 * 130 * @param source the source file to copy 131 * @param destination the destination to copy the source to 132 * @throws IOException if there are IO errors or the source does not exist 133 */ 134 private static void copyFile( File source, File destination ) throws IOException 135 { 136 LOG.debug( "copyFile(): source = {}, destination = {}", source, destination ); 137 138 if ( !destination.getParentFile().exists() && !destination.getParentFile().mkdirs() ) 139 { 140 throw new IOException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, destination.getParentFile() ) ); 141 } 142 143 if ( !source.getParentFile().exists() ) 144 { 145 throw new FileNotFoundException( I18n.err( I18n.ERR_509, source.getAbsolutePath() ) ); 146 } 147 148 try ( Writer out = Files.newBufferedWriter( destination.toPath(), StandardCharsets.UTF_8 ); 149 BufferedReader in = Files.newBufferedReader( source.toPath(), StandardCharsets.UTF_8 ); ) 150 { 151 String line; 152 153 while ( null != ( line = in.readLine() ) ) 154 { 155 out.write( line + "\n" ); 156 } 157 158 out.flush(); 159 } 160 } 161 162 163 /** 164 * Extracts the LDIF schema resource from a Jar. 165 * 166 * @param resource the LDIF schema resource 167 * @throws IOException if there are IO errors 168 */ 169 private static void extractFromJar( File outputDirectory, String resource ) throws IOException 170 { 171 byte[] buf = new byte[512]; 172 173 try ( InputStream in = DefaultSchemaLdifExtractor.getUniqueResourceAsStream( resource, 174 "LDIF file in config repository" ) ) 175 { 176 File destination = new File( outputDirectory, resource ); 177 178 /* 179 * Do not overwrite an LDIF file if it has already been extracted. 180 */ 181 if ( destination.exists() ) 182 { 183 return; 184 } 185 186 if ( !destination.getParentFile().exists() && !destination.getParentFile().mkdirs() ) 187 { 188 throw new IOException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, 189 destination.getParentFile() ) ); 190 } 191 192 try ( OutputStream out = Files.newOutputStream( destination.toPath() ) ) 193 { 194 while ( in.available() > 0 ) 195 { 196 int readCount = in.read( buf ); 197 out.write( buf, 0, readCount ); 198 } 199 out.flush(); 200 } 201 } 202 } 203 204 205 /** 206 * Calculates the destination file. 207 * 208 * @param resource the source file 209 * @return the destination file's parent directory 210 */ 211 private static File getDestinationFile( File outputDirectory, File resource ) 212 { 213 File parent = resource.getParentFile(); 214 Stack<String> fileComponentStack = new Stack<>(); 215 fileComponentStack.push( resource.getName() ); 216 217 while ( parent != null ) 218 { 219 if ( parent.getName().equals( "config" ) ) 220 { 221 // All LDIF files besides the config.ldif are under the 222 // config/config base path. So we need to add one more 223 // schema component to all LDIF files minus this config.ldif 224 fileComponentStack.push( "config" ); 225 226 return assembleDestinationFile( outputDirectory, fileComponentStack ); 227 } 228 229 fileComponentStack.push( parent.getName() ); 230 231 if ( parent.equals( parent.getParentFile() ) || parent.getParentFile() == null ) 232 { 233 throw new IllegalStateException( I18n.err( I18n.ERR_510 ) ); 234 } 235 236 parent = parent.getParentFile(); 237 } 238 239 throw new IllegalStateException( I18n.err( I18n.ERR_511 ) ); 240 } 241 242 243 /** 244 * Assembles the destination file by appending file components previously 245 * pushed on the fileComponentStack argument. 246 * 247 * @param fileComponentStack stack containing pushed file components 248 * @return the assembled destination file 249 */ 250 private static File assembleDestinationFile( File outputDirectory, Stack<String> fileComponentStack ) 251 { 252 File destinationFile = outputDirectory.getAbsoluteFile(); 253 254 while ( !fileComponentStack.isEmpty() ) 255 { 256 destinationFile = new File( destinationFile, fileComponentStack.pop() ); 257 } 258 259 return destinationFile; 260 } 261 262 263 /** 264 * extracts or overwrites the configuration LDIF file and returns the absolute path of this file 265 * 266 * @param configDir the directory where the config file should be extracted to 267 * @param file The file containing the configuration 268 * @param overwrite flag to indicate to overwrite the config file if already present in the given config directory 269 * @return complete path of the config file on disk 270 */ 271 public static String extractSingleFileConfig( File configDir, String file, boolean overwrite ) 272 { 273 if ( file == null ) 274 { 275 file = LDIF_CONFIG_FILE; 276 } 277 278 File configFile = new File( configDir, file ); 279 280 if ( !configDir.exists() ) 281 { 282 LOG.debug( "creating non existing config directory {}", configDir.getAbsolutePath() ); 283 if ( !configDir.mkdir() ) 284 { 285 throw new RuntimeException( 286 new IOException( I18n.err( I18n.ERR_112_COULD_NOT_CREATE_DIRECTORY, configDir ) ) ); 287 } 288 } 289 else 290 { 291 if ( configFile.exists() && !overwrite ) 292 { 293 LOG.warn( "config file already exists, returning, cause overwrite flag was set to false" ); 294 return configFile.getAbsolutePath(); 295 } 296 } 297 298 try 299 { 300 301 URL configUrl = LdifConfigExtractor.class.getClassLoader().getResource( file ); 302 303 LOG.debug( "URL of the config ldif file {}", configUrl ); 304 305 byte[] buf = new byte[1024 * 1024]; 306 307 308 try ( InputStream in = configUrl.openStream(); 309 Writer writer = Files.newBufferedWriter( configFile.toPath(), StandardCharsets.UTF_8 ) ) 310 { 311 while ( true ) 312 { 313 int read = in.read( buf ); 314 315 if ( read <= 0 ) 316 { 317 break; 318 } 319 320 String s = Strings.utf8ToString( buf, 0, read ); 321 writer.write( s ); 322 } 323 } 324 325 LOG.info( "successfully extracted the config file {}", configFile.getAbsoluteFile() ); 326 327 return configFile.getAbsolutePath(); 328 } 329 catch ( Exception e ) 330 { 331 throw new RuntimeException( e ); 332 } 333 } 334}