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.server.configuration;
021
022
023 import java.io.File;
024 import java.io.FileFilter;
025 import java.io.IOException;
026 import java.util.ArrayList;
027 import java.util.List;
028 import java.util.Set;
029
030 import javax.naming.NamingException;
031
032 import org.apache.commons.lang.StringUtils;
033 import org.apache.directory.server.constants.ApacheSchemaConstants;
034 import org.apache.directory.server.constants.ServerDNConstants;
035 import org.apache.directory.server.core.DefaultDirectoryService;
036 import org.apache.directory.server.core.DirectoryService;
037 import org.apache.directory.server.core.entry.ClonedServerEntry;
038 import org.apache.directory.server.core.partition.Partition;
039 import org.apache.directory.server.core.partition.impl.btree.BTreePartition;
040 import org.apache.directory.server.core.partition.ldif.LdifPartition;
041 import org.apache.directory.server.core.schema.SchemaPartition;
042 import org.apache.directory.server.i18n.I18n;
043 import org.apache.directory.server.ldap.LdapServer;
044 import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
045 import org.apache.directory.server.protocol.shared.store.LdifLoadFilter;
046 import org.apache.directory.shared.ldap.constants.SchemaConstants;
047 import org.apache.directory.shared.ldap.entry.ServerEntry;
048 import org.apache.directory.shared.ldap.name.DN;
049 import org.apache.directory.shared.ldap.schema.SchemaManager;
050 import org.apache.directory.shared.ldap.schema.ldif.extractor.SchemaLdifExtractor;
051 import org.apache.directory.shared.ldap.schema.ldif.extractor.impl.DefaultSchemaLdifExtractor;
052 import org.apache.directory.shared.ldap.schema.loader.ldif.LdifSchemaLoader;
053 import org.apache.directory.shared.ldap.schema.manager.impl.DefaultSchemaManager;
054 import org.apache.directory.shared.ldap.schema.registries.SchemaLoader;
055 import org.apache.directory.shared.ldap.util.ExceptionUtils;
056 import org.apache.directory.shared.ldap.util.StringTools;
057 import org.slf4j.Logger;
058 import org.slf4j.LoggerFactory;
059
060
061 /**
062 * Apache Directory Server top level.
063 *
064 * @org.apache.xbean.XBean
065 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
066 * @version $Rev$
067 */
068 public class ApacheDS
069 {
070 private static final Logger LOG = LoggerFactory.getLogger( ApacheDS.class.getName() );
071
072 /** Default delay between two flushes to the backend */
073 private static final long DEFAULT_SYNC_PERIOD_MILLIS = 20000;
074
075 /** Wainting period between two flushes to the backend */
076 private long synchPeriodMillis = DEFAULT_SYNC_PERIOD_MILLIS;
077
078 /** Directory where are stored the LDIF files to be loaded at startup */
079 private File ldifDirectory;
080
081 private final List<LdifLoadFilter> ldifFilters = new ArrayList<LdifLoadFilter>();
082
083 /** The LDAP server protocol handler */
084 private final LdapServer ldapServer;
085
086 /** The directory service */
087 private DirectoryService directoryService;
088
089
090 /**
091 * Creates a new instance of the ApacheDS server
092 *
093 * @param directoryService
094 * @param ldapServer
095 */
096 public ApacheDS( LdapServer ldapServer ) throws Exception
097 {
098 LOG.info( "Starting the Apache Directory Server" );
099
100 this.ldapServer = ldapServer;
101
102 directoryService = ldapServer.getDirectoryService();
103
104 if ( directoryService == null )
105 {
106 directoryService = new DefaultDirectoryService();
107 }
108 }
109
110
111 /**
112 * Start the server :
113 * <li>initialize the DirectoryService</li>
114 * <li>start the LDAP server</li>
115 * <li>start the LDAPS server</li>
116 *
117 * @throws NamingException If the server cannot be started
118 * @throws IOException If an IO error occured while reading some file
119 */
120 public void startup() throws Exception
121 {
122 LOG.debug( "Starting the server" );
123
124 initSchema();
125
126 SchemaManager schemaManager = directoryService.getSchemaManager();
127
128 if ( ! directoryService.isStarted() )
129 {
130 // inject the schema manager and set the partition directory
131 // once the CiDIT gets done we need not do this kind of dirty hack
132 Set<? extends Partition> partitions = directoryService.getPartitions();
133
134 for( Partition p : partitions )
135 {
136 if( p instanceof BTreePartition )
137 {
138 ( ( BTreePartition ) p ).setPartitionDir( new File( directoryService.getWorkingDirectory(), p.getId() ) );
139 }
140
141 if( p.getSchemaManager() == null )
142 {
143 LOG.info( "setting the schema manager for partition {}", p.getSuffix() );
144 p.setSchemaManager( schemaManager );
145 }
146 }
147
148 Partition sysPartition = directoryService.getSystemPartition();
149
150 if( sysPartition instanceof BTreePartition )
151 {
152 ( ( BTreePartition ) sysPartition ).setPartitionDir( new File( directoryService.getWorkingDirectory(), sysPartition.getId() ) );
153 }
154
155 if( sysPartition.getSchemaManager() == null )
156 {
157 LOG.info( "setting the schema manager for partition {}", sysPartition.getSuffix() );
158 sysPartition.setSchemaManager( schemaManager );
159 }
160
161 // Start the directory service if not started yet
162 LOG.debug( "1. Starting the DirectoryService" );
163 directoryService.startup();
164 }
165
166 // Load the LDIF files - if any - into the server
167 loadLdifs();
168
169 // Start the LDAP server
170 if ( ldapServer != null && ! ldapServer.isStarted() )
171 {
172 LOG.debug( "3. Starting the LDAP server" );
173 ldapServer.start();
174 }
175
176 LOG.debug( "Server successfully started" );
177 }
178
179
180 public boolean isStarted()
181 {
182 if ( ldapServer != null )
183 {
184 return ( ldapServer.isStarted() );
185 }
186
187 return directoryService.isStarted();
188 }
189
190
191 public void shutdown() throws Exception
192 {
193 if ( ldapServer != null && ldapServer.isStarted() )
194 {
195 ldapServer.stop();
196 }
197
198 directoryService.shutdown();
199 }
200
201
202 public LdapServer getLdapServer()
203 {
204 return ldapServer;
205 }
206
207
208 public DirectoryService getDirectoryService()
209 {
210 return directoryService;
211 }
212
213
214 public long getSynchPeriodMillis()
215 {
216 return synchPeriodMillis;
217 }
218
219
220 public void setSynchPeriodMillis( long synchPeriodMillis )
221 {
222 LOG.info( "Set the synchPeriodMillis to {}", synchPeriodMillis );
223 this.synchPeriodMillis = synchPeriodMillis;
224 }
225
226
227 /**
228 * Get the directory where
229 * @return
230 */
231 public File getLdifDirectory()
232 {
233 return ldifDirectory;
234 }
235
236
237 public void setLdifDirectory( File ldifDirectory )
238 {
239 LOG.info( "The LDIF directory file is {}", ldifDirectory.getAbsolutePath() );
240 this.ldifDirectory = ldifDirectory;
241 }
242
243
244 // ----------------------------------------------------------------------
245 // From CoreContextFactory: presently in intermediate step but these
246 // methods will be moved to the appropriate protocol service eventually.
247 // This is here simply to start to remove the JNDI dependency then further
248 // refactoring will be needed to place these where they belong.
249 // ----------------------------------------------------------------------
250
251
252 /**
253 * Check that the entry where are stored the loaded Ldif files is created.
254 *
255 * If not, create it.
256 *
257 * The files are stored in ou=loadedLdifFiles,ou=configuration,ou=system
258 */
259 private void ensureLdifFileBase() throws Exception
260 {
261 DN dn = new DN( ServerDNConstants.LDIF_FILES_DN );
262 ServerEntry entry = null;
263
264 try
265 {
266 entry = directoryService.getAdminSession().lookup( dn );
267 }
268 catch( Exception e )
269 {
270 LOG.info( "Failure while looking up {}. The entry will be created now.", ServerDNConstants.LDIF_FILES_DN, e );
271 }
272
273 if ( entry == null )
274 {
275 entry = directoryService.newEntry( new DN( ServerDNConstants.LDIF_FILES_DN ) );
276 entry.add( SchemaConstants.OU_AT, "loadedLdifFiles" );
277 entry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
278
279 directoryService.getAdminSession().add( entry );
280 }
281 }
282
283
284 /**
285 * Create a string containing a hex dump of the loaded ldif file name.
286 *
287 * It is associated with the attributeType wrt to the underlying system.
288 */
289 private DN buildProtectedFileEntryDn( File ldif ) throws Exception
290 {
291 String fileSep = File.separatorChar == '\\' ?
292 ApacheSchemaConstants.WINDOWS_FILE_AT :
293 ApacheSchemaConstants.UNIX_FILE_AT;
294
295 return new DN( fileSep +
296 "=" +
297 StringTools.dumpHexPairs( StringTools.getBytesUtf8( getCanonical( ldif ) ) ) +
298 "," +
299 ServerDNConstants.LDIF_FILES_DN );
300 }
301
302
303 private void addFileEntry( File ldif ) throws Exception
304 {
305 String rdnAttr = File.separatorChar == '\\' ?
306 ApacheSchemaConstants.WINDOWS_FILE_AT :
307 ApacheSchemaConstants.UNIX_FILE_AT;
308 String oc = File.separatorChar == '\\' ? ApacheSchemaConstants.WINDOWS_FILE_OC : ApacheSchemaConstants.UNIX_FILE_OC;
309
310 ServerEntry entry = directoryService.newEntry( buildProtectedFileEntryDn( ldif ) );
311 entry.add( rdnAttr, getCanonical( ldif ) );
312 entry.add( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, oc );
313 directoryService.getAdminSession().add( entry );
314 }
315
316
317 private String getCanonical( File file )
318 {
319 String canonical;
320
321 try
322 {
323 canonical = file.getCanonicalPath();
324 }
325 catch ( IOException e )
326 {
327 LOG.error( I18n.err( I18n.ERR_179 ), e );
328 return null;
329 }
330
331 return StringUtils.replace( canonical, "\\", "\\\\" );
332 }
333
334
335 /**
336 * Load a ldif into the directory.
337 *
338 * @param root The context in which we will inject the entries
339 * @param ldifFile The ldif file to read
340 * @throws NamingException If something went wrong while loading the entries
341 */
342 private void loadLdif( File ldifFile ) throws Exception
343 {
344 ClonedServerEntry fileEntry = null;
345 try
346 {
347 fileEntry = directoryService.getAdminSession().lookup( buildProtectedFileEntryDn( ldifFile ) );
348 }
349 catch( Exception e )
350 {
351 // if does not exist
352 }
353
354 if ( fileEntry != null )
355 {
356 String time = ((ClonedServerEntry)fileEntry).getOriginalEntry().get( SchemaConstants.CREATE_TIMESTAMP_AT ).getString();
357 LOG.info( "Load of LDIF file '" + getCanonical( ldifFile )
358 + "' skipped. It has already been loaded on " + time + "." );
359 }
360 else
361 {
362 LdifFileLoader loader = new LdifFileLoader( directoryService.getAdminSession(), ldifFile, ldifFilters );
363 int count = loader.execute();
364 LOG.info( "Loaded " + count + " entries from LDIF file '" + getCanonical( ldifFile ) + "'" );
365 addFileEntry( ldifFile );
366 }
367 }
368
369
370 /**
371 * Load the ldif files if there are some
372 */
373 public void loadLdifs() throws Exception
374 {
375 // LOG and bail if property not set
376 if ( ldifDirectory == null )
377 {
378 LOG.info( "LDIF load directory not specified. No LDIF files will be loaded." );
379 return;
380 }
381
382 // LOG and bail if LDIF directory does not exists
383 if ( ! ldifDirectory.exists() )
384 {
385 LOG.warn( "LDIF load directory '{}' does not exist. No LDIF files will be loaded.",
386 getCanonical( ldifDirectory ) );
387 return;
388 }
389
390
391 DN dn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN );
392
393 // Must normalize the dn or - IllegalStateException!
394 dn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
395
396 ensureLdifFileBase();
397
398 // if ldif directory is a file try to load it
399 if ( ldifDirectory.isFile() )
400 {
401 if ( LOG.isInfoEnabled() )
402 {
403 LOG.info( "LDIF load directory '{}' is a file. Will attempt to load as LDIF.",
404 getCanonical( ldifDirectory ) );
405 }
406
407 try
408 {
409 loadLdif( ldifDirectory );
410 }
411 catch ( Exception ne )
412 {
413 // If the file can't be read, log the error, and stop
414 // loading LDIFs.
415 LOG.error( I18n.err( I18n.ERR_180, ldifDirectory.getAbsolutePath(), ne.getLocalizedMessage() ) );
416 throw ne;
417 }
418 }
419 else
420 {
421 // get all the ldif files within the directory (should be sorted alphabetically)
422 File[] ldifFiles = ldifDirectory.listFiles( new FileFilter()
423 {
424 public boolean accept( File pathname )
425 {
426 boolean isLdif = pathname.getName().toLowerCase().endsWith( ".ldif" );
427 return pathname.isFile() && pathname.canRead() && isLdif;
428 }
429 } );
430
431 // LOG and bail if we could not find any LDIF files
432 if ( ( ldifFiles == null ) || ( ldifFiles.length == 0 ) )
433 {
434 LOG.warn( "LDIF load directory '{}' does not contain any LDIF files. No LDIF files will be loaded.",
435 getCanonical( ldifDirectory ) );
436 return;
437 }
438
439 // load all the ldif files and load each one that is loaded
440 for ( File ldifFile : ldifFiles )
441 {
442 try
443 {
444 LOG.info( "Loading LDIF file '{}'", ldifFile.getName() );
445 loadLdif( ldifFile );
446 }
447 catch ( Exception ne )
448 {
449 // If the file can't be read, log the error, and stop
450 // loading LDIFs.
451 LOG.error( I18n.err( I18n.ERR_180, ldifFile.getAbsolutePath(), ne.getLocalizedMessage() ) );
452 throw ne;
453 }
454 }
455 }
456 }
457
458
459 /**
460 * initialize the schema partition by loading the schema LDIF files
461 *
462 * @throws Exception in case of any problems while extracting and writing the schema files
463 */
464 private void initSchema() throws Exception
465 {
466 SchemaPartition schemaPartition = directoryService.getSchemaService().getSchemaPartition();
467
468 // Init the LdifPartition
469 LdifPartition ldifPartition = new LdifPartition();
470 String workingDirectory = directoryService.getWorkingDirectory().getPath();
471 ldifPartition.setWorkingDirectory( workingDirectory + "/schema" );
472
473 // Extract the schema on disk (a brand new one) and load the registries
474 File schemaRepository = new File( workingDirectory, "schema" );
475
476 if( schemaRepository.exists() )
477 {
478 LOG.info( "schema partition already exists, skipping schema extraction" );
479 }
480 else
481 {
482 SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor( new File( workingDirectory ) );
483 extractor.extractOrCopy();
484 }
485
486 schemaPartition.setWrappedPartition( ldifPartition );
487
488 SchemaLoader loader = new LdifSchemaLoader( schemaRepository );
489 SchemaManager schemaManager = new DefaultSchemaManager( loader );
490 directoryService.setSchemaManager( schemaManager );
491
492 // We have to load the schema now, otherwise we won't be able
493 // to initialize the Partitions, as we won't be able to parse
494 // and normalize their suffix DN
495 schemaManager.loadAllEnabled();
496
497 schemaPartition.setSchemaManager( schemaManager );
498
499 List<Throwable> errors = schemaManager.getErrors();
500
501 if ( errors.size() != 0 )
502 {
503 throw new Exception( I18n.err( I18n.ERR_317, ExceptionUtils.printErrors( errors ) ) );
504 }
505 }
506
507 }