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    }