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.core;
021    
022    
023    import java.io.BufferedReader;
024    import java.io.File;
025    import java.io.IOException;
026    import java.io.StringReader;
027    import java.util.ArrayList;
028    import java.util.Arrays;
029    import java.util.HashSet;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Set;
033    import java.util.UUID;
034    
035    import javax.naming.directory.Attributes;
036    
037    import org.apache.directory.server.constants.ServerDNConstants;
038    import org.apache.directory.server.core.authn.AuthenticationInterceptor;
039    import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
040    import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
041    import org.apache.directory.server.core.changelog.ChangeLog;
042    import org.apache.directory.server.core.changelog.ChangeLogEvent;
043    import org.apache.directory.server.core.changelog.ChangeLogInterceptor;
044    import org.apache.directory.server.core.changelog.DefaultChangeLog;
045    import org.apache.directory.server.core.changelog.Tag;
046    import org.apache.directory.server.core.changelog.TaggableSearchableChangeLogStore;
047    import org.apache.directory.server.core.collective.CollectiveAttributeInterceptor;
048    import org.apache.directory.server.core.event.EventInterceptor;
049    import org.apache.directory.server.core.event.EventService;
050    import org.apache.directory.server.core.exception.ExceptionInterceptor;
051    import org.apache.directory.server.core.interceptor.Interceptor;
052    import org.apache.directory.server.core.interceptor.InterceptorChain;
053    import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
054    import org.apache.directory.server.core.interceptor.context.AddOperationContext;
055    import org.apache.directory.server.core.interceptor.context.BindOperationContext;
056    import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
057    import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
058    import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
059    import org.apache.directory.server.core.journal.DefaultJournal;
060    import org.apache.directory.server.core.journal.Journal;
061    import org.apache.directory.server.core.journal.JournalInterceptor;
062    import org.apache.directory.server.core.normalization.NormalizationInterceptor;
063    import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
064    import org.apache.directory.server.core.partition.DefaultPartitionNexus;
065    import org.apache.directory.server.core.partition.Partition;
066    import org.apache.directory.server.core.partition.PartitionNexus;
067    import org.apache.directory.server.core.referral.ReferralInterceptor;
068    import org.apache.directory.server.core.replication.ReplicationConfiguration;
069    import org.apache.directory.server.core.schema.DefaultSchemaService;
070    import org.apache.directory.server.core.schema.SchemaInterceptor;
071    import org.apache.directory.server.core.schema.SchemaService;
072    import org.apache.directory.server.core.security.TlsKeyGenerator;
073    import org.apache.directory.server.core.subtree.SubentryInterceptor;
074    import org.apache.directory.server.core.trigger.TriggerInterceptor;
075    import org.apache.directory.server.i18n.I18n;
076    import org.apache.directory.shared.ldap.NotImplementedException;
077    import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
078    import org.apache.directory.shared.ldap.constants.SchemaConstants;
079    import org.apache.directory.shared.ldap.csn.Csn;
080    import org.apache.directory.shared.ldap.csn.CsnFactory;
081    import org.apache.directory.shared.ldap.cursor.Cursor;
082    import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
083    import org.apache.directory.shared.ldap.entry.Entry;
084    import org.apache.directory.shared.ldap.entry.EntryAttribute;
085    import org.apache.directory.shared.ldap.entry.Modification;
086    import org.apache.directory.shared.ldap.entry.ServerEntry;
087    import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
088    import org.apache.directory.shared.ldap.exception.LdapException;
089    import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
090    import org.apache.directory.shared.ldap.ldif.ChangeType;
091    import org.apache.directory.shared.ldap.ldif.LdifEntry;
092    import org.apache.directory.shared.ldap.ldif.LdifReader;
093    import org.apache.directory.shared.ldap.name.DN;
094    import org.apache.directory.shared.ldap.name.RDN;
095    import org.apache.directory.shared.ldap.schema.SchemaManager;
096    import org.apache.directory.shared.ldap.schema.normalizers.OidNormalizer;
097    import org.apache.directory.shared.ldap.util.DateUtils;
098    import org.apache.directory.shared.ldap.util.StringTools;
099    import org.slf4j.Logger;
100    import org.slf4j.LoggerFactory;
101    
102    
103    /**
104     * Default implementation of {@link DirectoryService}.
105     * 
106     * @org.apache.xbean.XBean
107     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
108     */
109    public class DefaultDirectoryService implements DirectoryService
110    {
111        /** The logger */
112        private static final Logger LOG = LoggerFactory.getLogger( DefaultDirectoryService.class );
113        
114        private SchemaService schemaService;
115        
116        /** A reference on the SchemaManager */
117        private SchemaManager schemaManager;
118        
119        /** the root nexus */
120        private DefaultPartitionNexus partitionNexus;
121    
122        /** whether or not server is started for the first time */
123        private boolean firstStart;
124    
125        /** The interceptor (or interceptor chain) for this service */
126        private InterceptorChain interceptorChain;
127    
128        /** whether or not this instance has been shutdown */
129        private boolean started;
130    
131        /** the change log service */
132        private ChangeLog changeLog;
133        
134        /** the journal service */
135        private Journal journal;
136        
137        /** 
138         * the interface used to perform various operations on this 
139         * DirectoryService
140         */
141        private OperationManager operationManager = new DefaultOperationManager( this );
142    
143        /** the distinguished name of the administrative user */
144        private DN adminDn;
145        
146        /** session used as admin for internal operations */
147        private CoreSession adminSession;
148        
149        /** The referral manager */
150        private ReferralManager referralManager;
151        
152        /** A flag to tell if the userPassword attribute's value must be hidden */
153        private boolean passwordHidden = false;
154        
155        /** The service's CSN factory */
156        private CsnFactory csnFactory;
157        
158        /** The directory instance replication ID */
159        private int replicaId;
160        
161        /** The replication configuration structure */
162        private ReplicationConfiguration replicationConfig;
163    
164        /** remove me after implementation is completed */
165        private static final String PARTIAL_IMPL_WARNING =
166                "WARNING: the changelog is only partially operational and will revert\n" +
167                "state without consideration of who made the original change.  All reverting " +
168                "changes are made by the admin user.\n Furthermore the used controls are not at " +
169                "all taken into account";
170    
171        
172        /** The delay to wait between each sync on disk */
173        private long syncPeriodMillis;
174        
175        /** The default delay to wait between sync on disk : 15 seconds */
176        private static final long DEFAULT_SYNC_PERIOD = 15000;
177    
178        /** */
179        private Thread workerThread;
180        
181        /** The sync worker thread */
182        private SynchWorker worker = new SynchWorker();
183    
184       
185        /** The default timeLimit : 100 entries */
186        public static final int MAX_SIZE_LIMIT_DEFAULT = 100;
187    
188        /** The default timeLimit : 10 seconds */
189        public static final int MAX_TIME_LIMIT_DEFAULT = 10000;
190    
191        /** The instance Id */
192        private String instanceId;
193        
194        /** The server working directory */
195        private File workingDirectory = new File( "server-work" );
196        
197        /** 
198         * A flag used to shutdown the VM when stopping the server. Useful
199         * when the server is standalone. If the server is embedded, we don't
200         * want to shutdown the VM
201         */
202        private boolean exitVmOnShutdown = true; // allow by default
203        
204        /** A flag used to indicate that a shutdown hook has been installed */
205        private boolean shutdownHookEnabled = true; // allow by default
206        
207        /** Manage anonymous access to entries other than the RootDSE */
208        private boolean allowAnonymousAccess = true; // allow by default
209        
210        /** Manage the basic access control checks */
211        private boolean accessControlEnabled; // off by default
212        
213        /** Manage the operational attributes denormalization */
214        private boolean denormalizeOpAttrsEnabled; // off by default
215        
216        /** The list of declared interceptors */
217        private List<Interceptor> interceptors;
218        
219        /** The System partition */
220        private Partition systemPartition;
221        
222        /** The set of all declared partitions */
223        private Set<Partition> partitions = new HashSet<Partition>();
224        
225        /** A list of LDIF entries to inject at startup */
226        private List<? extends LdifEntry> testEntries = new ArrayList<LdifEntry>(); // List<Attributes>
227        
228        /** The event service */
229        private EventService eventService;
230        
231        /** The maximum size for an incoming PDU */
232        private int maxPDUSize = Integer.MAX_VALUE;
233    
234    
235        
236        /**
237         * The synchronizer thread. It flush data on disk periodically.
238         */
239        class SynchWorker implements Runnable
240        {
241            final Object lock = new Object();
242            
243            /** A flag to stop the thread */
244            boolean stop;
245    
246    
247            /**
248             * The main loop
249             */
250            public void run()
251            {
252                while ( !stop )
253                {
254                    synchronized ( lock )
255                    {
256                        try
257                        {
258                            lock.wait( syncPeriodMillis );
259                        }
260                        catch ( InterruptedException e )
261                        {
262                            LOG.warn( "SynchWorker failed to wait on lock.", e );
263                        }
264                    }
265    
266                    try
267                    {
268                        partitionNexus.sync();
269                    }
270                    catch ( Exception e )
271                    {
272                        LOG.error( I18n.err( I18n.ERR_74 ), e );
273                    }
274                }
275            }
276        }
277        
278        
279        // ------------------------------------------------------------------------
280        // Constructor
281        // ------------------------------------------------------------------------
282        
283        
284        /**
285         * Creates a new instance of the directory service.
286         */
287        public DefaultDirectoryService() throws Exception
288        {
289            setDefaultInterceptorConfigurations();
290            changeLog = new DefaultChangeLog();
291            journal = new DefaultJournal();
292            syncPeriodMillis = DEFAULT_SYNC_PERIOD;
293            csnFactory = new CsnFactory( replicaId );
294            schemaService = new DefaultSchemaService();
295        }
296    
297    
298        // ------------------------------------------------------------------------
299        // C O N F I G U R A T I O N   M E T H O D S
300        // ------------------------------------------------------------------------
301        
302        
303        public void setInstanceId( String instanceId )
304        {
305            this.instanceId = instanceId;
306        }
307    
308    
309        public String getInstanceId()
310        {
311            return instanceId;
312        }
313    
314    
315        /**
316         * Gets the {@link Partition}s used by this DirectoryService.
317         *
318         * @return the set of partitions used
319         */
320        public Set<? extends Partition> getPartitions()
321        {
322            Set<Partition> cloned = new HashSet<Partition>();
323            cloned.addAll( partitions );
324            return cloned;
325        }
326    
327    
328        /**
329         * Sets {@link Partition}s used by this DirectoryService.
330         *
331         * @param partitions the partitions to used
332         */
333        public void setPartitions( Set<? extends Partition> partitions )
334        {
335            Set<Partition> cloned = new HashSet<Partition>();
336            cloned.addAll( partitions );
337            Set<String> names = new HashSet<String>();
338            
339            for ( Partition partition : cloned )
340            {
341                String id = partition.getId();
342                
343                if ( names.contains( id ) )
344                {
345                    LOG.warn( "Encountered duplicate partition {} identifier.", id );
346                }
347                
348                names.add( id );
349            }
350    
351            this.partitions = cloned;
352        }
353    
354    
355        /**
356         * Returns <tt>true</tt> if access control checks are enabled.
357         *
358         * @return true if access control checks are enabled, false otherwise
359         */
360        public boolean isAccessControlEnabled()
361        {
362            return accessControlEnabled;
363        }
364    
365    
366        /**
367         * Sets whether to enable basic access control checks or not.
368         *
369         * @param accessControlEnabled true to enable access control checks, false otherwise
370         */
371        public void setAccessControlEnabled( boolean accessControlEnabled )
372        {
373            this.accessControlEnabled = accessControlEnabled;
374        }
375    
376    
377        /**
378         * Returns <tt>true</tt> if anonymous access is allowed on entries besides the RootDSE.
379         * If the access control subsystem is enabled then access to some entries may not be
380         * allowed even when full anonymous access is enabled.
381         *
382         * @return true if anonymous access is allowed on entries besides the RootDSE, false
383         * if anonymous access is allowed to all entries.
384         */
385        public boolean isAllowAnonymousAccess()
386        {
387            return allowAnonymousAccess;
388        }
389    
390    
391        /**
392         * Sets whether to allow anonymous access to entries other than the RootDSE.  If the
393         * access control subsystem is enabled then access to some entries may not be allowed
394         * even when full anonymous access is enabled.
395         *
396         * @param enableAnonymousAccess true to enable anonymous access, false to disable it
397         */
398        public void setAllowAnonymousAccess( boolean enableAnonymousAccess )
399        {
400            this.allowAnonymousAccess = enableAnonymousAccess;
401        }
402    
403    
404        /**
405         * Returns interceptors in the server.
406         *
407         * @return the interceptors in the server.
408         */
409        public List<Interceptor> getInterceptors()
410        {
411            List<Interceptor> cloned = new ArrayList<Interceptor>();
412            cloned.addAll( interceptors );
413            return cloned;
414        }
415    
416    
417        /**
418         * Sets the interceptors in the server.
419         *
420         * @param interceptors the interceptors to be used in the server.
421         */
422        public void setInterceptors( List<Interceptor> interceptors ) 
423        {
424            Set<String> names = new HashSet<String>();
425            
426            for ( Interceptor interceptor : interceptors )
427            {
428                String name = interceptor.getName();
429                
430                if ( names.contains( name ) )
431                {
432                    LOG.warn( "Encountered duplicate definitions for {} interceptor", interceptor.getName() );
433                }
434                names.add( name );
435            }
436    
437            this.interceptors = interceptors;
438        }
439    
440    
441        /**
442         * Returns test directory entries({@link LdifEntry}) to be loaded while
443         * bootstrapping.
444         *
445         * @return test entries to load during bootstrapping
446         */
447        public List<LdifEntry> getTestEntries()
448        {
449            List<LdifEntry> cloned = new ArrayList<LdifEntry>();
450            cloned.addAll( testEntries );
451            return cloned;
452        }
453    
454    
455        /**
456         * Sets test directory entries({@link Attributes}) to be loaded while
457         * bootstrapping.
458         *
459         * @param testEntries the test entries to load while bootstrapping
460         */
461        public void setTestEntries( List<? extends LdifEntry> testEntries )
462        {
463            //noinspection MismatchedQueryAndUpdateOfCollection
464            List<LdifEntry> cloned = new ArrayList<LdifEntry>();
465            cloned.addAll( testEntries );
466            this.testEntries = testEntries;
467        }
468    
469    
470        /**
471         * Returns working directory (counterpart of <tt>var/lib</tt>) where partitions are
472         * stored by default.
473         *
474         * @return the directory where partition's are stored.
475         */
476        public File getWorkingDirectory()
477        {
478            return workingDirectory;
479        }
480    
481    
482        /**
483         * Sets working directory (counterpart of <tt>var/lib</tt>) where partitions are stored
484         * by default.
485         *
486         * @param workingDirectory the directory where the server's partitions are stored by default.
487         */
488        public void setWorkingDirectory( File workingDirectory )
489        {
490            this.workingDirectory = workingDirectory;
491        }
492    
493    
494        public void setShutdownHookEnabled( boolean shutdownHookEnabled )
495        {
496            this.shutdownHookEnabled = shutdownHookEnabled;
497        }
498    
499    
500        public boolean isShutdownHookEnabled()
501        {
502            return shutdownHookEnabled;
503        }
504    
505    
506        public void setExitVmOnShutdown( boolean exitVmOnShutdown )
507        {
508            this.exitVmOnShutdown = exitVmOnShutdown;
509        }
510    
511    
512        public boolean isExitVmOnShutdown()
513        {
514            return exitVmOnShutdown;
515        }
516    
517    
518        public void setSystemPartition( Partition systemPartition )
519        {
520            this.systemPartition = systemPartition;
521        }
522    
523    
524        public Partition getSystemPartition()
525        {
526            return systemPartition;
527        }
528    
529    
530        /**
531         * return true if the operational attributes must be normalized when returned
532         */
533        public boolean isDenormalizeOpAttrsEnabled()
534        {
535            return denormalizeOpAttrsEnabled;
536        }
537    
538    
539        /**
540         * Sets whether the operational attributes are denormalized when returned
541         * @param denormalizeOpAttrsEnabled The flag value
542         */
543        public void setDenormalizeOpAttrsEnabled( boolean denormalizeOpAttrsEnabled )
544        {
545            this.denormalizeOpAttrsEnabled = denormalizeOpAttrsEnabled;
546        }
547    
548    
549        /**
550         * {@inheritDoc}
551         */
552        public ChangeLog getChangeLog()
553        {
554            return changeLog;
555        }
556    
557    
558        /**
559         * {@inheritDoc}
560         */
561        public Journal getJournal()
562        {
563            return journal;
564        }
565    
566    
567        /**
568         * {@inheritDoc}
569         */
570        public void setChangeLog( ChangeLog changeLog )
571        {
572            this.changeLog = changeLog;
573        }
574    
575    
576        /**
577         * {@inheritDoc}
578         */
579        public void setJournal( Journal journal )
580        {
581            this.journal = journal;
582        }
583    
584    
585        public void addPartition( Partition partition ) throws Exception
586        {
587            partition.setSchemaManager( schemaManager );
588            partitions.add( partition );
589    
590            if ( ! started )
591            {
592                return;
593            }
594    
595            AddContextPartitionOperationContext addPartitionCtx = 
596                new AddContextPartitionOperationContext( adminSession, partition );
597            partitionNexus.addContextPartition( addPartitionCtx );
598        }
599    
600    
601        public void removePartition( Partition partition ) throws Exception
602        {
603            partitions.remove( partition );
604    
605            if ( ! started )
606            {
607                return;
608            }
609    
610            RemoveContextPartitionOperationContext removePartitionCtx =
611                    new RemoveContextPartitionOperationContext( adminSession, partition.getSuffixDn() );
612            partitionNexus.removeContextPartition( removePartitionCtx );
613        }
614    
615    
616        // ------------------------------------------------------------------------
617        // BackendSubsystem Interface Method Implementations
618        // ------------------------------------------------------------------------
619    
620    
621        private void setDefaultInterceptorConfigurations()
622        {
623            // Set default interceptor chains
624            List<Interceptor> list = new ArrayList<Interceptor>();
625    
626            list.add( new NormalizationInterceptor() );
627            list.add( new AuthenticationInterceptor() );
628            list.add( new ReferralInterceptor() );
629            list.add( new AciAuthorizationInterceptor() );
630            list.add( new DefaultAuthorizationInterceptor() );
631            list.add( new ExceptionInterceptor() );
632            list.add( new ChangeLogInterceptor() );
633            list.add( new OperationalAttributeInterceptor() );
634            list.add( new SchemaInterceptor() );
635            list.add( new SubentryInterceptor() );
636            list.add( new CollectiveAttributeInterceptor() );
637            list.add( new EventInterceptor() );
638            list.add( new TriggerInterceptor() );
639            list.add( new JournalInterceptor() );
640    
641            setInterceptors( list );
642        }
643    
644        
645        public CoreSession getAdminSession()
646        {
647            return adminSession;
648        }
649        
650        
651        public CoreSession getSession() 
652        {
653            return new DefaultCoreSession( new LdapPrincipal(), this );
654        }
655        
656        
657        public CoreSession getSession( LdapPrincipal principal )
658        {
659            return new DefaultCoreSession( principal, this );
660        }
661        
662        
663        public CoreSession getSession( DN principalDn, byte[] credentials ) 
664            throws Exception
665        {
666            if ( ! started )
667            {
668                throw new IllegalStateException( "Service has not started." );
669            }
670    
671            BindOperationContext bindContext = new BindOperationContext( null );
672            bindContext.setCredentials( credentials );
673            bindContext.setDn( principalDn );
674            operationManager.bind( bindContext );
675            
676            return bindContext.getSession();
677        }
678        
679        
680        public CoreSession getSession( DN principalDn, byte[] credentials, String saslMechanism, String saslAuthId ) 
681            throws Exception
682        {
683            if ( ! started )
684            {
685                throw new IllegalStateException( "Service has not started." );
686            }
687    
688            BindOperationContext bindContext = new BindOperationContext( null );
689            bindContext.setCredentials( credentials );
690            bindContext.setDn( principalDn );
691            bindContext.setSaslMechanism( saslMechanism );
692            operationManager.bind( bindContext );
693            
694            return bindContext.getSession();
695        }
696    
697    
698        public long revert() throws Exception
699        {
700            if ( changeLog == null || ! changeLog.isEnabled() )
701            {
702                throw new IllegalStateException( I18n.err( I18n.ERR_310 ) );
703            }
704    
705            Tag latest = changeLog.getLatest();
706            
707            if ( null != latest )
708            {
709                if ( latest.getRevision() < changeLog.getCurrentRevision() )
710                {
711                    return revert( latest.getRevision() );
712                }
713                else
714                {
715                    LOG.info( "Ignoring request to revert without changes since the latest tag." );
716                    return changeLog.getCurrentRevision();
717                }
718            }
719    
720            throw new IllegalStateException( I18n.err( I18n.ERR_311 ) );
721        }
722    
723    
724        /**
725         * We handle the ModDN/ModRDN operation for the revert here. 
726         */
727        private void moddn( DN oldDn, DN newDn, boolean delOldRdn ) throws Exception
728        {
729            if ( oldDn.size() == 0 )
730            {
731                throw new LdapNoPermissionException( I18n.err( I18n.ERR_312 ) );
732            }
733    
734            // calculate parents
735            DN oldBase = ( DN ) oldDn.clone();
736            oldBase.remove( oldDn.size() - 1 );
737            DN newBase = ( DN ) newDn.clone();
738            newBase.remove( newDn.size() - 1 );
739    
740            // Compute the RDN for each of the DN
741            RDN newRdn = newDn.getRdn( newDn.size() - 1 );
742            RDN oldRdn = oldDn.getRdn( oldDn.size() - 1 );
743    
744            /*
745             * We need to determine if this rename operation corresponds to a simple
746             * RDN name change or a move operation.  If the two names are the same
747             * except for the RDN then it is a simple modifyRdn operation.  If the
748             * names differ in size or have a different baseDN then the operation is
749             * a move operation.  Furthermore if the RDN in the move operation 
750             * changes it is both an RDN change and a move operation.
751             */
752            if ( ( oldDn.size() == newDn.size() ) && oldBase.equals( newBase ) )
753            {
754                adminSession.rename( oldDn, newRdn, delOldRdn );
755            }
756            else
757            {
758                DN target = ( DN ) newDn.clone();
759                target.remove( newDn.size() - 1 );
760    
761                if ( newRdn.equals( oldRdn ) )
762                {
763                    adminSession.move( oldDn, target );
764                }
765                else
766                {
767                    adminSession.moveAndRename( oldDn, target, new RDN( newRdn ), delOldRdn );
768                }
769            }
770        }
771        
772        
773        public long revert( long revision ) throws Exception
774        {
775            if ( changeLog == null || ! changeLog.isEnabled() )
776            {
777                throw new IllegalStateException( I18n.err( I18n.ERR_310 ) );
778            }
779    
780            if ( revision < 0 )
781            {
782                throw new IllegalArgumentException( I18n.err( I18n.ERR_239 ) );
783            }
784    
785            if ( revision >= changeLog.getChangeLogStore().getCurrentRevision() )
786            {
787                throw new IllegalArgumentException( I18n.err( I18n.ERR_314 ) );
788            }
789    
790            Cursor<ChangeLogEvent> cursor = changeLog.getChangeLogStore().findAfter( revision );
791    
792            /*
793             * BAD, BAD, BAD!!!
794             *
795             * No synchronization no nothing.  Just getting this to work for now
796             * so we can revert tests.  Any production grade use of this feature
797             * needs to synchronize on all changes while the revert is in progress.
798             *
799             * How about making this operation transactional?
800             *
801             * First of all just stop using JNDI and construct the operations to
802             * feed into the interceptor pipeline.
803             * 
804             * TODO review this code.
805             */
806    
807            try
808            {
809                LOG.warn( PARTIAL_IMPL_WARNING );
810                cursor.afterLast();
811                
812                while ( cursor.previous() ) // apply ldifs in reverse order
813                {
814                    ChangeLogEvent event = cursor.get();
815                    List<LdifEntry> reverses = event.getReverseLdifs();
816                    
817                    for ( LdifEntry reverse:reverses )
818                    {
819                        switch( reverse.getChangeType().getChangeType() )
820                        {
821                            case ChangeType.ADD_ORDINAL :
822                                adminSession.add( 
823                                    new DefaultServerEntry( schemaManager, reverse.getEntry() ), true ); 
824                                break;
825                                
826                            case ChangeType.DELETE_ORDINAL :
827                                adminSession.delete( reverse.getDn(), true );
828                                break;
829                                
830                            case ChangeType.MODIFY_ORDINAL :
831                                List<Modification> mods = reverse.getModificationItems();
832        
833                                adminSession.modify( reverse.getDn(), mods, true );
834                                break;
835                                
836                            case ChangeType.MODDN_ORDINAL :
837                                // NO BREAK - both ModDN and ModRDN handling is the same
838                            
839                            case ChangeType.MODRDN_ORDINAL :
840                                DN forwardDn = event.getForwardLdif().getDn();
841                                DN reverseDn = reverse.getDn();
842                                
843                                moddn( reverseDn, forwardDn, reverse.isDeleteOldRdn() );
844        
845                                break;
846                                
847                            default:
848                                LOG.error( I18n.err( I18n.ERR_75 ) );
849                                throw new NotImplementedException( I18n.err( I18n.ERR_76, reverse.getChangeType() ) );
850                        }
851                    }
852                }
853            }
854            catch ( IOException e )
855            {
856                String message = I18n.err( I18n.ERR_77, revision );
857                LOG.error( message );
858                throw new LdapException( message );
859            }
860    
861            return changeLog.getCurrentRevision();
862        }
863    
864        
865        public OperationManager getOperationManager()
866        {
867            return operationManager;
868        }
869        
870    
871        /**
872         * @throws Exception if the LDAP server cannot be started
873         */
874        public synchronized void startup() throws Exception
875        {
876            if ( started )
877            {
878                return;
879            }
880    
881            if ( shutdownHookEnabled )
882            {
883                Runtime.getRuntime().addShutdownHook( new Thread( new Runnable()
884                {
885                    public void run()
886                    {
887                        try
888                        {
889                            shutdown();
890                        }
891                        catch ( Exception e )
892                        {
893                            LOG.warn( "Failed to shut down the directory service: "
894                                + DefaultDirectoryService.this.instanceId, e );
895                        }
896                    }
897                }, "ApacheDS Shutdown Hook (" + instanceId + ')' ) );
898    
899                LOG.info( "ApacheDS shutdown hook has been registered with the runtime." );
900            }
901            else if ( LOG.isWarnEnabled() )
902            {
903                LOG.warn( "ApacheDS shutdown hook has NOT been registered with the runtime."
904                    + "  This default setting for standalone operation has been overriden." );
905            }
906    
907            initialize();
908            showSecurityWarnings();
909            
910            // Start the sync thread if required
911            if ( syncPeriodMillis > 0 )
912            {
913                workerThread = new Thread( worker, "SynchWorkerThread" );
914                workerThread.start();
915            }
916    
917            
918            started = true;
919    
920            if ( !testEntries.isEmpty() )
921            {
922                createTestEntries();
923            }
924        }
925    
926    
927        public synchronized void sync() throws Exception
928        {
929            if ( !started )
930            {
931                return;
932            }
933    
934            this.changeLog.sync();
935            this.partitionNexus.sync();
936        }
937    
938    
939        public synchronized void shutdown() throws Exception
940        {
941            if ( !started )
942            {
943                return;
944            }
945    
946            // --------------------------------------------------------------------
947            // Shutdown the changelog
948            // --------------------------------------------------------------------
949            changeLog.sync();
950            changeLog.destroy();
951            
952            // --------------------------------------------------------------------
953            // Shutdown the journal if enabled
954            // --------------------------------------------------------------------
955            if ( journal.isEnabled() )
956            {
957                journal.destroy();
958            }
959    
960            // --------------------------------------------------------------------
961            // Shutdown the partition
962            // --------------------------------------------------------------------
963    
964            partitionNexus.sync();
965            partitionNexus.destroy();
966            
967            // --------------------------------------------------------------------
968            // Shutdown the sync thread
969            // --------------------------------------------------------------------
970            if ( workerThread != null )
971            {
972                worker.stop = true;
973                
974                synchronized ( worker.lock )
975                {
976                    worker.lock.notify();
977                }
978        
979                while ( workerThread.isAlive() )
980                {
981                    LOG.info( "Waiting for SynchWorkerThread to die." );
982                    workerThread.join( 500 );
983                }
984            }
985    
986            
987            // --------------------------------------------------------------------
988            // And shutdown the server
989            // --------------------------------------------------------------------
990            interceptorChain.destroy();
991            started = false;
992            setDefaultInterceptorConfigurations();
993        }
994    
995        
996        /**
997         * @return The referral manager
998         */
999        public ReferralManager getReferralManager()
1000        {
1001            return referralManager;
1002        }
1003    
1004        /**
1005         * Set the referralManager
1006         * @param referralManager The initialized referralManager
1007         */
1008        public void setReferralManager( ReferralManager referralManager )
1009        {
1010            this.referralManager = referralManager;
1011        }
1012        
1013        
1014        /**
1015         * @return the SchemaManager
1016         */
1017        public SchemaManager getSchemaManager()
1018        {
1019            return schemaManager;
1020        }
1021        
1022        
1023        /**
1024         * Set the SchemaManager instance.
1025         *
1026         * @param schemaManager The schemaManager
1027         */
1028        public void setSchemaManager( SchemaManager schemaManager )
1029        {
1030            this.schemaManager = schemaManager;
1031        }
1032    
1033    
1034        public SchemaService getSchemaService()
1035        {
1036            return schemaService;
1037        }
1038    
1039    
1040        public void setSchemaService( SchemaService schemaService )
1041        {
1042            this.schemaService = schemaService;
1043        }
1044    
1045    
1046        public DefaultPartitionNexus getPartitionNexus()
1047        {
1048            return partitionNexus;
1049        }
1050    
1051    
1052        public InterceptorChain getInterceptorChain()
1053        {
1054            return interceptorChain;
1055        }
1056    
1057    
1058        public boolean isFirstStart()
1059        {
1060            return firstStart;
1061        }
1062    
1063    
1064        public boolean isStarted()
1065        {
1066            return started;
1067        }
1068    
1069    
1070        public ServerEntry newEntry( DN dn ) 
1071        {
1072            return new DefaultServerEntry( schemaManager, dn );
1073        }
1074        
1075    
1076        /**
1077         * Returns true if we had to create the bootstrap entries on the first
1078         * start of the server.  Otherwise if all entries exist, meaning none
1079         * had to be created, then we are not starting for the first time.
1080         *
1081         * @return true if the bootstrap entries had to be created, false otherwise
1082         * @throws Exception if entries cannot be created
1083         */
1084        private boolean createBootstrapEntries() throws Exception
1085        {
1086            boolean firstStart = false;
1087            
1088            // -------------------------------------------------------------------
1089            // create admin entry
1090            // -------------------------------------------------------------------
1091    
1092            /*
1093             * If the admin entry is there, then the database was already created
1094             */
1095            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, adminDn ) ) )
1096            {
1097                firstStart = true;
1098    
1099                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, adminDn );
1100                
1101                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, 
1102                                    SchemaConstants.TOP_OC,
1103                                    SchemaConstants.PERSON_OC,
1104                                    SchemaConstants.ORGANIZATIONAL_PERSON_OC,
1105                                    SchemaConstants.INET_ORG_PERSON_OC );
1106    
1107                serverEntry.put( SchemaConstants.UID_AT, PartitionNexus.ADMIN_UID );
1108                serverEntry.put( SchemaConstants.USER_PASSWORD_AT, PartitionNexus.ADMIN_PASSWORD_BYTES );
1109                serverEntry.put( SchemaConstants.DISPLAY_NAME_AT, "Directory Superuser" );
1110                serverEntry.put( SchemaConstants.CN_AT, "system administrator" );
1111                serverEntry.put( SchemaConstants.SN_AT, "administrator" );
1112                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1113                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1114                serverEntry.put( SchemaConstants.DISPLAY_NAME_AT, "Directory Superuser" );
1115                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1116                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1117    
1118                TlsKeyGenerator.addKeyPair( serverEntry );
1119                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1120            }
1121    
1122            // -------------------------------------------------------------------
1123            // create system users area
1124            // -------------------------------------------------------------------
1125    
1126            Map<String,OidNormalizer> oidsMap = schemaManager.getNormalizerMapping();
1127            DN userDn = new DN( ServerDNConstants.USERS_SYSTEM_DN );
1128            userDn.normalize( oidsMap );
1129            
1130            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, userDn ) ) )
1131            {
1132                firstStart = true;
1133    
1134                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, userDn );
1135                
1136                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, 
1137                                    SchemaConstants.TOP_OC,
1138                                    SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1139    
1140                serverEntry.put( SchemaConstants.OU_AT, "users" );
1141                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1142                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1143                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1144                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1145    
1146                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1147            }
1148    
1149            // -------------------------------------------------------------------
1150            // create system groups area
1151            // -------------------------------------------------------------------
1152    
1153            DN groupDn = new DN( ServerDNConstants.GROUPS_SYSTEM_DN );
1154            groupDn.normalize( oidsMap );
1155            
1156            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, groupDn ) ) )
1157            {
1158                firstStart = true;
1159    
1160                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, groupDn );
1161                
1162                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, 
1163                                    SchemaConstants.TOP_OC,
1164                                    SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1165    
1166                serverEntry.put( SchemaConstants.OU_AT, "groups" );
1167                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1168                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1169                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1170                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1171    
1172                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1173            }
1174    
1175            // -------------------------------------------------------------------
1176            // create administrator group
1177            // -------------------------------------------------------------------
1178    
1179            DN name = new DN( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
1180            name.normalize( oidsMap );
1181            
1182            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, name ) ) )
1183            {
1184                firstStart = true;
1185    
1186                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, name );
1187                
1188                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, 
1189                                    SchemaConstants.TOP_OC,
1190                                    SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC );
1191    
1192                serverEntry.put( SchemaConstants.CN_AT, "Administrators" );
1193                serverEntry.put( SchemaConstants.UNIQUE_MEMBER_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1194                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1195                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1196                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1197                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1198    
1199                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1200    
1201                // TODO - confirm if we need this at all since the 
1202                // group cache on initialization after this stage will
1203                // search the directory for all the groups anyway
1204                
1205    //            Interceptor authzInterceptor = interceptorChain.get( AciAuthorizationInterceptor.class.getName() );
1206    //            
1207    //            if ( authzInterceptor == null )
1208    //            {
1209    //                LOG.error( "The Authorization service is null : this is not allowed" );
1210    //                throw new NamingException( "The Authorization service is null" );
1211    //            }
1212    //            
1213    //            if ( !( authzInterceptor instanceof AciAuthorizationInterceptor ) )
1214    //            {
1215    //                LOG.error( "The Authorization service is not set correctly : '{}' is an incorect interceptor",
1216    //                    authzInterceptor.getClass().getName() );
1217    //                throw new NamingException( "The Authorization service is incorrectly set" );
1218    //                
1219    //            }
1220    //
1221    //            AciAuthorizationInterceptor authzSrvc = ( AciAuthorizationInterceptor ) authzInterceptor;
1222    //            authzSrvc.cacheNewGroup( name, serverEntry );
1223            }
1224    
1225            // -------------------------------------------------------------------
1226            // create system configuration area
1227            // -------------------------------------------------------------------
1228    
1229            DN configurationDn = new DN( "ou=configuration,ou=system" );
1230            configurationDn.normalize( oidsMap );
1231            
1232            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, configurationDn ) ) )
1233            {
1234                firstStart = true;
1235    
1236                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, configurationDn );
1237                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1238    
1239                serverEntry.put( SchemaConstants.OU_AT, "configuration" );
1240                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1241                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1242                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1243                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1244    
1245                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1246            }
1247    
1248            // -------------------------------------------------------------------
1249            // create system configuration area for partition information
1250            // -------------------------------------------------------------------
1251    
1252            DN partitionsDn = new DN( "ou=partitions,ou=configuration,ou=system" );
1253            partitionsDn.normalize( oidsMap );
1254            
1255            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, partitionsDn ) ) )
1256            {
1257                firstStart = true;
1258    
1259                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, partitionsDn );
1260                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1261                serverEntry.put( SchemaConstants.OU_AT, "partitions" );
1262                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1263                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1264                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1265                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1266    
1267                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1268            }
1269    
1270            // -------------------------------------------------------------------
1271            // create system configuration area for services
1272            // -------------------------------------------------------------------
1273    
1274            DN servicesDn = new DN( "ou=services,ou=configuration,ou=system" );
1275            servicesDn.normalize( oidsMap );
1276            
1277            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, servicesDn ) ) )
1278            {
1279                firstStart = true;
1280    
1281                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, servicesDn );
1282                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1283    
1284                serverEntry.put( SchemaConstants.OU_AT, "services" );
1285                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1286                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1287                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1288                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1289    
1290                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1291            }
1292    
1293            // -------------------------------------------------------------------
1294            // create system configuration area for interceptors
1295            // -------------------------------------------------------------------
1296    
1297            DN interceptorsDn = new DN( "ou=interceptors,ou=configuration,ou=system" );
1298            interceptorsDn.normalize( oidsMap );
1299            
1300            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, interceptorsDn ) ) )
1301            {
1302                firstStart = true;
1303    
1304                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, interceptorsDn );
1305                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.ORGANIZATIONAL_UNIT_OC );
1306    
1307                serverEntry.put( SchemaConstants.OU_AT, "interceptors" );
1308                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1309                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1310                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1311                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1312    
1313                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1314            }
1315    
1316            // -------------------------------------------------------------------
1317            // create system preferences area
1318            // -------------------------------------------------------------------
1319    
1320            DN sysPrefRootDn = new DN( ServerDNConstants.SYSPREFROOT_SYSTEM_DN );
1321            sysPrefRootDn.normalize( oidsMap );
1322            
1323            if ( !partitionNexus.hasEntry( new EntryOperationContext( adminSession, sysPrefRootDn ) ) )
1324            {
1325                firstStart = true;
1326    
1327                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, sysPrefRootDn );
1328                serverEntry.put( SchemaConstants.OBJECT_CLASS_AT, 
1329                    SchemaConstants.TOP_OC, 
1330                    SchemaConstants.ORGANIZATIONAL_UNIT_OC,
1331                    SchemaConstants.EXTENSIBLE_OBJECT_OC );
1332    
1333                serverEntry.put( "prefNodeName", "sysPrefRoot" );
1334                serverEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1335                serverEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
1336                serverEntry.add( SchemaConstants.ENTRY_CSN_AT, getCSN().toString() );
1337                serverEntry.add( SchemaConstants.ENTRY_UUID_AT, UUID.randomUUID().toString() );
1338    
1339                partitionNexus.add( new AddOperationContext( adminSession, serverEntry ) );
1340            }
1341    
1342            return firstStart;
1343        }
1344    
1345    
1346        /**
1347         * Displays security warning messages if any possible secutiry issue is found.
1348         * @throws Exception if there are failures parsing and accessing internal structures
1349         */
1350        private void showSecurityWarnings() throws Exception
1351        {
1352            // Warn if the default password is not changed.
1353            boolean needToChangeAdminPassword = false;
1354    
1355            DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN );
1356            adminDn.normalize( schemaManager.getNormalizerMapping() );
1357            
1358            ServerEntry adminEntry = partitionNexus.lookup( new LookupOperationContext( adminSession, adminDn ) );
1359            Object userPassword = adminEntry.get( SchemaConstants.USER_PASSWORD_AT ).get();
1360            
1361            if ( userPassword instanceof byte[] )
1362            {
1363                needToChangeAdminPassword = Arrays.equals( PartitionNexus.ADMIN_PASSWORD_BYTES, ( byte[] ) userPassword );
1364            }
1365            else if ( userPassword.toString().equals( PartitionNexus.ADMIN_PASSWORD_STRING ) )
1366            {
1367                needToChangeAdminPassword = PartitionNexus.ADMIN_PASSWORD_STRING.equals( userPassword.toString() );
1368            }
1369    
1370            if ( needToChangeAdminPassword )
1371            {
1372                LOG.warn( "You didn't change the admin password of directory service " + "instance '" + instanceId + "'.  "
1373                    + "Please update the admin password as soon as possible " + "to prevent a possible security breach." );
1374            }
1375        }
1376    
1377    
1378        /**
1379         * Adds test entries into the core.
1380         *
1381         * @todo this may no longer be needed when JNDI is not used for bootstrapping
1382         * 
1383         * @throws Exception if the creation of test entries fails.
1384         */
1385        private void createTestEntries() throws Exception
1386        {
1387            for ( LdifEntry testEntry : testEntries )
1388            {
1389                try
1390                {
1391                    LdifEntry ldifEntry = testEntry.clone();
1392                    Entry entry = ldifEntry.getEntry();
1393                    String dn = ldifEntry.getDn().getName();
1394    
1395                    try
1396                    {
1397                        getAdminSession().add( new DefaultServerEntry( schemaManager, entry ) ); 
1398                    }
1399                    catch ( Exception e )
1400                    {
1401                        LOG.warn( dn + " test entry already exists.", e );
1402                    }
1403                }
1404                catch ( CloneNotSupportedException cnse )
1405                {
1406                    LOG.warn( "Cannot clone the entry ", cnse );
1407                }
1408            }
1409        }
1410    
1411    
1412        /**
1413         * Kicks off the initialization of the entire system.
1414         *
1415         * @throws Exception if there are problems along the way
1416         */
1417        private void initialize() throws Exception
1418        {
1419            if ( LOG.isDebugEnabled() )
1420            {
1421                LOG.debug( "---> Initializing the DefaultDirectoryService " );
1422            }
1423    
1424            // triggers partition to load schema fully from schema partition
1425            schemaService.initialize();
1426            schemaService.getSchemaPartition().initialize();
1427            partitions.add( schemaService.getSchemaPartition() );
1428            systemPartition.getSuffixDn().normalize( schemaManager.getNormalizerMapping() );
1429    
1430            adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN ).normalize( schemaManager.getNormalizerMapping() );
1431            adminDn.normalize( schemaManager.getNormalizerMapping() );
1432            adminSession = new DefaultCoreSession( new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), this );
1433            
1434            // @TODO - NOTE: Need to find a way to instantiate without dependency on DPN
1435            partitionNexus = new DefaultPartitionNexus( new DefaultServerEntry( schemaManager, DN.EMPTY_DN ) );
1436            partitionNexus.setDirectoryService( this );
1437            partitionNexus.initialize( );
1438            //partitionNexus.addContextPartition( new AddContextPartitionOperationContext( adminSession, schemaService.getSchemaPartition() ) );
1439    
1440            // --------------------------------------------------------------------
1441            // Create all the bootstrap entries before initializing chain
1442            // --------------------------------------------------------------------
1443            
1444            firstStart = createBootstrapEntries();
1445    
1446            interceptorChain = new InterceptorChain();
1447            interceptorChain.init( this );
1448    
1449            // --------------------------------------------------------------------
1450            // Initialize the changeLog if it's enabled
1451            // --------------------------------------------------------------------
1452            
1453            if ( changeLog.isEnabled() )
1454            {
1455                changeLog.init( this );
1456                
1457                if( changeLog.isExposed() && changeLog.isTagSearchSupported() )
1458                {
1459                    String clSuffix = ( ( TaggableSearchableChangeLogStore ) changeLog.getChangeLogStore() ).getPartition().getSuffixDn().getName();
1460                    partitionNexus.getRootDSE( null ).getOriginalEntry().add( SchemaConstants.CHANGELOG_CONTEXT_AT, clSuffix );
1461                }
1462            }
1463            
1464            // --------------------------------------------------------------------
1465            // Initialize the journal if it's enabled
1466            // --------------------------------------------------------------------
1467            if ( journal.isEnabled() )
1468            {
1469                journal.init( this );
1470            }
1471    
1472            if ( LOG.isDebugEnabled() )
1473            {
1474                LOG.debug( "<--- DefaultDirectoryService initialized" );
1475            }
1476        }
1477        
1478        
1479        /**
1480         * Read an entry (without DN)
1481         * 
1482         * @param text The ldif format file
1483         * @return An entry.
1484         */
1485        private Entry readEntry( String text )
1486        {
1487            StringReader strIn = new StringReader( text );
1488            BufferedReader in = new BufferedReader( strIn );
1489    
1490            String line = null;
1491            Entry entry = new DefaultClientEntry();
1492    
1493            try
1494            {
1495                while ( ( line = in.readLine() ) != null )
1496                {
1497                    if ( line.length() == 0 )
1498                    {
1499                        continue;
1500                    }
1501    
1502                    String addedLine = line.trim();
1503    
1504                    if ( StringTools.isEmpty( addedLine ) )
1505                    {
1506                        continue;
1507                    }
1508    
1509                    EntryAttribute attribute = LdifReader.parseAttributeValue( addedLine );
1510                    EntryAttribute oldAttribute = entry.get( attribute.getId() );
1511    
1512                    if ( oldAttribute != null )
1513                    {
1514                        try
1515                        {
1516                            oldAttribute.add( attribute.get() );
1517                            entry.put( oldAttribute );
1518                        }
1519                        catch ( LdapException ne )
1520                        {
1521                            // Do nothing
1522                        }
1523                    }
1524                    else
1525                    {
1526                        try
1527                        {
1528                            entry.put( attribute );
1529                        }
1530                        catch ( LdapException ne )
1531                        {
1532                            // TODO do nothing ...
1533                        }
1534                    }
1535                }
1536            }
1537            catch (IOException ioe)
1538            {
1539                // Do nothing : we can't reach this point !
1540            }
1541    
1542            return entry;
1543        }
1544    
1545        
1546        /**
1547         * Create a new ServerEntry
1548         * 
1549         * @param ldif The String representing the attributes, as a LDIF file
1550         * @param dn The DN for this new entry
1551         */
1552        public ServerEntry newEntry( String ldif, String dn )
1553        {
1554            try
1555            {
1556                Entry entry = readEntry( ldif );
1557                DN newDn = new DN( dn );
1558                
1559                entry.setDn( newDn );
1560                
1561                // TODO Let's get rid of this Attributes crap
1562                ServerEntry serverEntry = new DefaultServerEntry( schemaManager, entry );
1563                return serverEntry;
1564            }
1565            catch ( Exception e )
1566            {
1567                LOG.error( I18n.err( I18n.ERR_78, ldif, dn ) );
1568                // do nothing
1569                return null;
1570            }
1571        }
1572    
1573    
1574        public EventService getEventService()
1575        {
1576            return eventService;
1577        }
1578    
1579    
1580        public void setEventService( EventService eventService )
1581        {
1582            this.eventService = eventService;
1583        }
1584        
1585        
1586        /**
1587         * {@inheritDoc}
1588         */
1589        public boolean isPasswordHidden()
1590        {
1591            return passwordHidden;
1592        }
1593        
1594        
1595        /**
1596         * {@inheritDoc}
1597         */
1598        public void setPasswordHidden( boolean passwordHidden )
1599        {
1600            this.passwordHidden = passwordHidden;
1601        }
1602    
1603    
1604        /**
1605         * @return The maximum allowed size for an incoming PDU
1606         */
1607        public int getMaxPDUSize()
1608        {
1609            return maxPDUSize;
1610        }
1611    
1612    
1613        /**
1614         * Set the maximum allowed size for an incoming PDU 
1615         * @param maxPDUSize A positive number of bytes for the PDU. A negative or
1616         * null value will be transformed to {@link Integer#MAX_VALUE}
1617         */
1618        public void setMaxPDUSize( int maxPDUSize )
1619        {
1620            if ( maxPDUSize <= 0 )
1621            {
1622                maxPDUSize = Integer.MAX_VALUE;
1623            }
1624            
1625            this.maxPDUSize = maxPDUSize;
1626        }
1627        
1628        
1629        /**
1630         * {@inheritDoc}
1631         */
1632        public Interceptor getInterceptor( String interceptorName )
1633        {
1634            for ( Interceptor interceptor:interceptors )
1635            {
1636                if ( interceptor.getName().equalsIgnoreCase( interceptorName ) )
1637                {
1638                    return interceptor;
1639                }
1640            }
1641            
1642            return null;
1643        }
1644    
1645    
1646        /**
1647         * Get a new CSN
1648         * @return The CSN generated for this directory service
1649         */
1650        public Csn getCSN()
1651        {
1652            return csnFactory.newInstance();
1653        }
1654    
1655    
1656        /**
1657         * @return the replicaId
1658         */
1659        public int getReplicaId()
1660        {
1661            return replicaId;
1662        }
1663    
1664    
1665        /**
1666         * @param replicaId the replicaId to set
1667         */
1668        public void setReplicaId( int replicaId )
1669        {
1670            if ( ( replicaId < 0 ) || ( replicaId > 999 ) )
1671            {
1672                LOG.error( I18n.err( I18n.ERR_79 ) );
1673                this.replicaId = 0;
1674            }
1675            else
1676            {
1677                this.replicaId = replicaId;
1678            }
1679        }
1680    
1681    
1682        public void setReplicationConfiguration( ReplicationConfiguration replicationConfig )
1683        {
1684            this.replicationConfig = replicationConfig;
1685            
1686        }
1687        
1688        
1689        /**
1690         * @return the replication configuration for this DirectoryService
1691         */
1692        public ReplicationConfiguration getReplicationConfiguration()
1693        {
1694            return replicationConfig;
1695        }
1696        
1697        
1698        /**
1699         * @return the syncPeriodMillis
1700         */
1701        public long getSyncPeriodMillis()
1702        {
1703            return syncPeriodMillis;
1704        }
1705    
1706    
1707        /**
1708         * @param syncPeriodMillis the syncPeriodMillis to set
1709         */
1710        public void setSyncPeriodMillis( long syncPeriodMillis )
1711        {
1712            this.syncPeriodMillis = syncPeriodMillis;
1713        }
1714    }