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