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}