View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.partition;
21  
22  
23  import org.apache.directory.server.constants.ServerDNConstants;
24  import org.apache.directory.server.core.CoreSession;
25  import org.apache.directory.server.core.DefaultCoreSession;
26  import org.apache.directory.server.core.DirectoryService;
27  import org.apache.directory.server.core.authn.LdapPrincipal;
28  import org.apache.directory.server.core.cursor.SingletonCursor;
29  import org.apache.directory.server.core.entry.ClonedServerEntry;
30  import org.apache.directory.server.core.entry.DefaultServerAttribute;
31  import org.apache.directory.server.core.entry.DefaultServerEntry;
32  import org.apache.directory.server.core.entry.ServerEntry;
33  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
34  import org.apache.directory.server.core.filtering.BaseEntryFilteringCursor;
35  import org.apache.directory.server.core.interceptor.context.AddContextPartitionOperationContext;
36  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
37  import org.apache.directory.server.core.interceptor.context.BindOperationContext;
38  import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
39  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
40  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
41  import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
42  import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext;
43  import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext;
44  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
45  import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext;
46  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
47  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
48  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
49  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
50  import org.apache.directory.server.core.interceptor.context.RemoveContextPartitionOperationContext;
51  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
52  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
53  import org.apache.directory.server.core.interceptor.context.UnbindOperationContext;
54  import org.apache.directory.server.xdbm.Index;
55  import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
56  import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
57  import org.apache.directory.server.core.partition.tree.BranchNode;
58  import org.apache.directory.server.core.partition.tree.LeafNode;
59  import org.apache.directory.server.core.partition.tree.Node;
60  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
61  import org.apache.directory.server.schema.registries.OidRegistry;
62  import org.apache.directory.server.schema.registries.Registries;
63  import org.apache.directory.shared.ldap.MultiException;
64  import org.apache.directory.shared.ldap.NotImplementedException;
65  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
66  import org.apache.directory.shared.ldap.constants.SchemaConstants;
67  import org.apache.directory.shared.ldap.entry.EntryAttribute;
68  import org.apache.directory.shared.ldap.entry.Value;
69  import org.apache.directory.shared.ldap.exception.LdapInvalidAttributeIdentifierException;
70  import org.apache.directory.shared.ldap.exception.LdapNameNotFoundException;
71  import org.apache.directory.shared.ldap.exception.LdapNoSuchAttributeException;
72  import org.apache.directory.shared.ldap.filter.ExprNode;
73  import org.apache.directory.shared.ldap.filter.PresenceNode;
74  import org.apache.directory.shared.ldap.message.CascadeControl;
75  import org.apache.directory.shared.ldap.message.EntryChangeControl;
76  import org.apache.directory.shared.ldap.message.ManageDsaITControl;
77  import org.apache.directory.shared.ldap.message.PersistentSearchControl;
78  import org.apache.directory.shared.ldap.message.SubentriesControl;
79  import org.apache.directory.shared.ldap.message.extended.NoticeOfDisconnect;
80  import org.apache.directory.shared.ldap.name.LdapDN;
81  import org.apache.directory.shared.ldap.schema.AttributeType;
82  import org.apache.directory.shared.ldap.schema.Normalizer;
83  import org.apache.directory.shared.ldap.schema.UsageEnum;
84  import org.apache.directory.shared.ldap.util.DateUtils;
85  import org.apache.directory.shared.ldap.util.NamespaceTools;
86  import org.apache.directory.shared.ldap.util.StringTools;
87  
88  import org.slf4j.Logger;
89  import org.slf4j.LoggerFactory;
90  
91  import javax.naming.ConfigurationException;
92  import javax.naming.NameNotFoundException;
93  import javax.naming.directory.SearchControls;
94  import javax.naming.ldap.LdapContext;
95  import java.io.IOException;
96  import java.util.ArrayList;
97  import java.util.Arrays;
98  import java.util.Collections;
99  import java.util.Enumeration;
100 import java.util.HashMap;
101 import java.util.HashSet;
102 import java.util.Iterator;
103 import java.util.List;
104 import java.util.Map;
105 import java.util.Properties;
106 import java.util.Set;
107 
108 
109 /**
110  * A nexus for partitions dedicated for storing entries specific to a naming
111  * context.
112  * 
113  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
114  * @version $Rev: 689588 $
115  */
116 public class DefaultPartitionNexus extends PartitionNexus
117 {
118     private static final Logger LOG = LoggerFactory.getLogger( DefaultPartitionNexus.class );
119 
120     /** Speedup for logs */
121     private static final boolean IS_DEBUG = LOG.isDebugEnabled();
122 
123     /** the vendorName string proudly set to: Apache Software Foundation*/
124     private static final String ASF = "Apache Software Foundation";
125 
126     /** the closed state of this partition */
127     private boolean initialized;
128 
129     private DirectoryService directoryService;
130 
131     /** the system partition */
132     private Partition system;
133 
134     /** the partitions keyed by normalized suffix strings */
135     private Map<String, Partition> partitions = new HashMap<String, Partition>();
136     
137     /** A structure to hold all the partitions */
138     private BranchNode partitionLookupTree = new BranchNode();
139     
140     /** the read only rootDSE attributes */
141     private final ServerEntry rootDSE;
142 
143     /** The global registries */
144     private Registries registries;
145     
146     /** The attributeType registry */
147     private AttributeTypeRegistry atRegistry;
148     
149     /** The OID registry */
150     private OidRegistry oidRegistry;
151 
152 
153     /**
154      * Creates the root nexus singleton of the entire system.  The root DSE has
155      * several attributes that are injected into it besides those that may
156      * already exist.  As partitions are added to the system more namingContexts
157      * attributes are added to the rootDSE.
158      *
159      * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
160      * @param rootDSE the root entry for the DSA
161      * @throws javax.naming.Exception on failure to initialize
162      */
163     public DefaultPartitionNexus( ServerEntry rootDSE ) throws Exception
164     {
165         // setup that root DSE
166         this.rootDSE = rootDSE;
167         
168         // Add the basic informations
169         rootDSE.put( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, ServerDNConstants.CN_SCHEMA_DN );
170         rootDSE.put( SchemaConstants.SUPPORTED_LDAP_VERSION_AT, "3" );
171         rootDSE.put( SchemaConstants.SUPPORTED_FEATURES_AT, SchemaConstants.FEATURE_ALL_OPERATIONAL_ATTRIBUTES );
172         rootDSE.put( SchemaConstants.SUPPORTED_EXTENSION_AT, NoticeOfDisconnect.EXTENSION_OID );
173 
174         // Add the supported controls
175         rootDSE.put( SchemaConstants.SUPPORTED_CONTROL_AT, 
176             PersistentSearchControl.CONTROL_OID,
177             EntryChangeControl.CONTROL_OID,
178             SubentriesControl.CONTROL_OID,
179             ManageDsaITControl.CONTROL_OID,
180             CascadeControl.CONTROL_OID );
181 
182         // Add the objectClasses
183         rootDSE.put( SchemaConstants.OBJECT_CLASS_AT,
184             SchemaConstants.TOP_OC,
185             SchemaConstants.EXTENSIBLE_OBJECT_OC );
186 
187         // Add the 'vendor' name and version infos
188         rootDSE.put( SchemaConstants.VENDOR_NAME_AT, ASF );
189 
190         Properties props = new Properties();
191         
192         try
193         {
194             props.load( getClass().getResourceAsStream( "version.properties" ) );
195         }
196         catch ( IOException e )
197         {
198             LOG.error( "failed to LOG version properties" );
199         }
200 
201         rootDSE.put( SchemaConstants.VENDOR_VERSION_AT, props.getProperty( "apacheds.version", "UNKNOWN" ) );
202     }
203 
204     
205     /**
206      * Always returns the string "NEXUS".
207      *
208      * @return the string "NEXUS"
209      */
210     public String getId()
211     {
212         return "NEXUS";
213     }
214 
215 
216     // -----------------------------------------------------------------------
217     // C O N F I G U R A T I O N   M E T H O D S
218     // -----------------------------------------------------------------------
219 
220 
221     /**
222      * Not supported!
223      *
224      * @throws UnsupportedOperationException everytime
225      */
226     public void setId( String id )
227     {
228         throw new UnsupportedOperationException( "The id cannot be set for the partition nexus." );
229     }
230 
231 
232     /**
233      * Always returns the empty String "".
234      * @return the empty String ""
235      */
236     public String getSuffix()
237     {
238         return "";
239     }
240 
241 
242     /**
243      * Unsupported operation on the Nexus.
244      * @throws UnsupportedOperationException everytime
245      */
246     public void setSuffix( String suffix )
247     {
248         throw new UnsupportedOperationException();
249     }
250 
251 
252     /**
253      * Not support!
254      */
255     public void setCacheSize( int cacheSize )
256     {
257         throw new UnsupportedOperationException( "You cannot set the cache size of the nexus" );
258     }
259 
260 
261     /**
262      * Not supported!
263      *
264      * @throws UnsupportedOperationException always
265      */
266     public int getCacheSize()
267     {
268         throw new UnsupportedOperationException( "There is no cache size associated with the nexus" );
269     }
270 
271 
272 
273     public void init( DirectoryService directoryService )
274         throws Exception
275     {
276         // NOTE: We ignore ContextPartitionConfiguration parameter here.
277         if ( initialized )
278         {
279             return;
280         }
281 
282         this.directoryService = directoryService;
283         registries = directoryService.getRegistries();
284         atRegistry = registries.getAttributeTypeRegistry();
285         oidRegistry = registries.getOidRegistry();
286         
287         initializeSystemPartition();
288         List<Partition> initializedPartitions = new ArrayList<Partition>();
289         initializedPartitions.add( 0, this.system );
290 
291         //noinspection unchecked
292         Iterator<? extends Partition> partitions = ( Iterator<? extends Partition> ) directoryService.getPartitions().iterator();
293         try
294         {
295             while ( partitions.hasNext() )
296             {
297                 Partition partition = partitions.next();
298                 LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
299                 adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
300                 CoreSession adminSession = new DefaultCoreSession( 
301                     new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
302 
303                 AddContextPartitionOperationContext opCtx = 
304                     new AddContextPartitionOperationContext( adminSession, partition );
305                 addContextPartition( opCtx );
306                 initializedPartitions.add( opCtx.getPartition() );
307             }
308             initialized = true;
309         }
310         finally
311         {
312             if ( !initialized )
313             {
314                 Iterator<Partition> i = initializedPartitions.iterator();
315                 while ( i.hasNext() )
316                 {
317                     Partition partition = i.next();
318                     i.remove();
319                     try
320                     {
321                         partition.destroy();
322                     }
323                     catch ( Exception e )
324                     {
325                         LOG.warn( "Failed to destroy a partition: " + partition.getSuffixDn(), e );
326                     }
327                     finally
328                     {
329                         unregister( partition );
330                     }
331                 }
332             }
333         }
334     }
335 
336 
337     private Partition initializeSystemPartition() throws Exception
338     {
339         // initialize system partition first
340         Partition override = directoryService.getSystemPartition();
341         
342         if ( override != null )
343         {
344             
345             // ---------------------------------------------------------------
346             // check a few things to make sure users configured it properly
347             // ---------------------------------------------------------------
348 
349             if ( ! override.getId().equals( "system" ) )
350             {
351                 throw new ConfigurationException( "System partition has wrong name: should be 'system' not '"
352                         + override.getId() + "'." );
353             }
354             
355             // add all attribute oids of index configs to a hashset
356             if ( override instanceof JdbmPartition )
357             {
358                 Set<Index<?,ServerEntry>> indices = ( ( JdbmPartition ) override ).getIndexedAttributes();
359                 Set<String> indexOids = new HashSet<String>();
360                 OidRegistry registry = registries.getOidRegistry();
361 
362                 for ( Index<?,ServerEntry> index : indices )
363                 {
364                     indexOids.add( registry.getOid( index.getAttributeId() ) );
365                 }
366 
367                 if ( ! indexOids.contains( registry.getOid( SchemaConstants.OBJECT_CLASS_AT ) ) )
368                 {
369                     LOG.warn( "CAUTION: You have not included objectClass as an indexed attribute" +
370                             "in the system partition configuration.  This will lead to poor " +
371                             "performance.  The server is automatically adding this index for you." );
372                     JdbmIndex<?,ServerEntry> index = new JdbmIndex<Object,ServerEntry>();
373                     index.setAttributeId( SchemaConstants.OBJECT_CLASS_AT );
374                     indices.add( index );
375                 }
376 
377                 ( ( JdbmPartition ) override ).setIndexedAttributes( indices );
378             }
379 
380             system = override;
381         }
382         else
383         {
384             system = new JdbmPartition();
385             system.setId( "system" );
386             system.setCacheSize( 500 );
387             system.setSuffix( ServerDNConstants.SYSTEM_DN );
388     
389             // Add objectClass attribute for the system partition
390             Set<Index<?,ServerEntry>> indexedAttrs = new HashSet<Index<?,ServerEntry>>();
391             indexedAttrs.add( new JdbmIndex<Object,ServerEntry>( SchemaConstants.OBJECT_CLASS_AT ) );
392             ( ( JdbmPartition ) system ).setIndexedAttributes( indexedAttrs );
393         }
394 
395         system.init( directoryService );
396         
397         
398         // Add root context entry for system partition
399         LdapDN systemSuffixDn = new LdapDN( ServerDNConstants.SYSTEM_DN );
400         systemSuffixDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
401         ServerEntry systemEntry = new DefaultServerEntry( registries, systemSuffixDn );
402 
403         // Add the ObjectClasses
404         systemEntry.put( SchemaConstants.OBJECT_CLASS_AT,
405             SchemaConstants.TOP_OC,
406             SchemaConstants.ORGANIZATIONAL_UNIT_OC,
407             SchemaConstants.EXTENSIBLE_OBJECT_OC
408             );
409         
410         // Add some operational attributes
411         systemEntry.put( SchemaConstants.CREATORS_NAME_AT, ServerDNConstants.ADMIN_SYSTEM_DN );
412         systemEntry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() );
413         systemEntry.put( NamespaceTools.getRdnAttribute( ServerDNConstants.SYSTEM_DN ),
414             NamespaceTools.getRdnValue( ServerDNConstants.SYSTEM_DN ) );
415         LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
416         adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
417         CoreSession adminSession = new DefaultCoreSession( 
418             new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
419         AddOperationContext addOperationContext = new AddOperationContext( adminSession, systemEntry );
420         system.add( addOperationContext );
421         
422         String key = system.getSuffixDn().toString();
423         
424         if ( partitions.containsKey( key ) )
425         {
426             throw new ConfigurationException( "Duplicate partition suffix: " + key );
427         }
428         
429         synchronized ( partitionLookupTree )
430         {
431             partitions.put( key, system );
432             partitionLookupTree.recursivelyAddPartition( partitionLookupTree, system.getSuffixDn(), 0, system );
433             EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
434             
435             if ( namingContexts == null )
436             {
437                 namingContexts = new DefaultServerAttribute( 
438                     registries.getAttributeTypeRegistry().lookup( SchemaConstants.NAMING_CONTEXTS_AT ), 
439                     system.getUpSuffixDn().getUpName() );
440                 rootDSE.put( namingContexts );
441             }
442             else
443             {
444                 namingContexts.add( system.getUpSuffixDn().getUpName() );
445             }
446         }
447 
448         return system;
449     }
450 
451 
452     public boolean isInitialized()
453     {
454         return initialized;
455     }
456 
457 
458     public synchronized void destroy()
459     {
460         if ( !initialized )
461         {
462             return;
463         }
464 
465         // make sure this loop is not fail fast so all backing stores can
466         // have an attempt at closing down and synching their cached entries
467         for ( String suffix : new HashSet<String>( this.partitions.keySet() ) )
468         {
469             try
470             {
471                 LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
472                 adminDn.normalize( registries.getAttributeTypeRegistry().getNormalizerMapping() );
473                 CoreSession adminSession = new DefaultCoreSession( 
474                     new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
475                 removeContextPartition( new RemoveContextPartitionOperationContext( 
476                     adminSession, new LdapDN( suffix ) ) );
477             }
478             catch ( Exception e )
479             {
480                 LOG.warn( "Failed to destroy a partition: " + suffix, e );
481             }
482         }
483 
484         initialized = false;
485     }
486 
487 
488     /**
489      * @see Partition#sync()
490      */
491     public void sync() throws Exception
492     {
493         MultiException error = null;
494 
495         for ( Partition partition : this.partitions.values() )
496         {
497             try
498             {
499                 partition.sync();
500             }
501             catch ( Exception e )
502             {
503                 LOG.warn( "Failed to flush partition data out.", e );
504                 if ( error == null )
505                 {
506                     //noinspection ThrowableInstanceNeverThrown
507                     error = new MultiException( "Grouping many exceptions on root nexus sync()" );
508                 }
509 
510                 // @todo really need to send this info to a monitor
511                 error.addThrowable( e );
512             }
513         }
514 
515         if ( error != null )
516         {
517             throw error;
518         }
519     }
520 
521 
522     // ------------------------------------------------------------------------
523     // ContextPartitionNexus Method Implementations
524     // ------------------------------------------------------------------------
525 
526     
527     public boolean compare( CompareOperationContext compareContext ) throws Exception
528     {
529         Partition partition = getPartition( compareContext.getDn() );
530         AttributeTypeRegistry registry = registries.getAttributeTypeRegistry();
531         
532         // complain if we do not recognize the attribute being compared
533         if ( !registry.hasAttributeType( compareContext.getOid() ) )
534         {
535             throw new LdapInvalidAttributeIdentifierException( compareContext.getOid() + " not found within the attributeType registry" );
536         }
537 
538         AttributeType attrType = registry.lookup( compareContext.getOid() );
539         
540         EntryAttribute attr = partition.lookup( compareContext.newLookupContext( 
541             compareContext.getDn() ) ).get( attrType.getName() );
542 
543         // complain if the attribute being compared does not exist in the entry
544         if ( attr == null )
545         {
546             throw new LdapNoSuchAttributeException();
547         }
548 
549         // see first if simple match without normalization succeeds
550         if ( attr.contains( (Value<?>)compareContext.getValue()  ) )
551         {
552             return true;
553         }
554 
555         // now must apply normalization to all values (attr and in request) to compare
556 
557         /*
558          * Get ahold of the normalizer for the attribute and normalize the request
559          * assertion value for comparisons with normalized attribute values.  Loop
560          * through all values looking for a match.
561          */
562         Normalizer normalizer = attrType.getEquality().getNormalizer();
563         Object reqVal = normalizer.normalize( ((Value<?>)compareContext.getValue()).get() );
564 
565         for ( Value<?> value:attr )
566         {
567             Object attrValObj = normalizer.normalize( value.get() );
568             
569             if ( attrValObj instanceof String )
570             {
571                 String attrVal = ( String ) attrValObj;
572                 if ( ( reqVal instanceof String ) && attrVal.equals( reqVal ) )
573                 {
574                     return true;
575                 }
576             }
577             else
578             {
579                 byte[] attrVal = ( byte[] ) attrValObj;
580                 if ( reqVal instanceof byte[] )
581                 {
582                     return Arrays.equals( attrVal, ( byte[] ) reqVal );
583                 }
584                 else if ( reqVal instanceof String )
585                 {
586                     return Arrays.equals( attrVal, StringTools.getBytesUtf8( ( String ) reqVal ) );
587                 }
588             }
589         }
590 
591         return false;
592     }
593 
594 
595     public synchronized void addContextPartition( AddContextPartitionOperationContext opContext ) throws Exception
596     {
597         Partition partition = opContext.getPartition();
598 
599         // Turn on default indices
600         String key = partition.getSuffix();
601         
602         if ( partitions.containsKey( key ) )
603         {
604             throw new ConfigurationException( "Duplicate partition suffix: " + key );
605         }
606 
607         if ( ! partition.isInitialized() )
608         {
609             partition.init( directoryService );
610         }
611         
612         synchronized ( partitionLookupTree )
613         {
614             LdapDN partitionSuffix = partition.getSuffixDn();
615             
616             if ( partitionSuffix == null )
617             {
618                 throw new ConfigurationException( "The current partition does not have any suffix: " + partition.getId() );
619             }
620             
621             partitions.put( partitionSuffix.toString(), partition );
622             partitionLookupTree.recursivelyAddPartition( partitionLookupTree, partition.getSuffixDn(), 0, partition );
623 
624             EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
625 
626             LdapDN partitionUpSuffix = partition.getUpSuffixDn();
627             
628             if ( partitionUpSuffix == null )
629             {
630                 throw new ConfigurationException( "The current partition does not have any user provided suffix: " + partition.getId() );
631             }
632             
633             if ( namingContexts == null )
634             {
635                 namingContexts = new DefaultServerAttribute( 
636                     registries.getAttributeTypeRegistry().lookup( SchemaConstants.NAMING_CONTEXTS_AT ), partitionUpSuffix.getUpName() );
637                 rootDSE.put( namingContexts );
638             }
639             else
640             {
641                 namingContexts.add( partitionUpSuffix.getUpName() );
642             }
643         }
644     }
645 
646 
647     public synchronized void removeContextPartition( RemoveContextPartitionOperationContext removeContextPartition ) throws Exception
648     {
649         String key = removeContextPartition.getDn().getNormName();
650         Partition partition = partitions.get( key );
651         
652         if ( partition == null )
653         {
654             throw new NameNotFoundException( "No partition with suffix: " + key );
655         }
656 
657         EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
658         
659         if ( namingContexts != null )
660         {
661             namingContexts.remove( partition.getUpSuffixDn().getUpName() );
662         }
663 
664         // Create a new partition list. 
665         // This is easier to create a new structure from scratch than to reorganize
666         // the current structure. As this structure is not modified often
667         // this is an acceptable solution.
668         synchronized ( partitionLookupTree )
669         {
670             partitions.remove( key );
671             partitionLookupTree = new BranchNode();
672             
673             for ( Partition part : partitions.values() )
674             {
675                 partitionLookupTree.recursivelyAddPartition( partitionLookupTree, part.getSuffixDn(), 0, partition );
676             }
677     
678             partition.sync();
679             partition.destroy();
680         }
681     }
682 
683 
684     public Partition getSystemPartition()
685     {
686         return system;
687     }
688 
689 
690     /**
691      * @see PartitionNexus#getLdapContext()
692      */
693     public LdapContext getLdapContext()
694     {
695         throw new NotImplementedException();
696     }
697 
698 
699     /**
700      * @see PartitionNexus#getMatchedName( GetMatchedNameOperationContext )
701      */
702     public LdapDN getMatchedName ( GetMatchedNameOperationContext matchedNameContext ) throws Exception
703     {
704         LdapDN dn = ( LdapDN ) matchedNameContext.getDn().clone();
705         
706         while ( dn.size() > 0 )
707         {
708             if ( hasEntry( new EntryOperationContext( matchedNameContext.getSession(), dn ) ) )
709             {
710                 return dn;
711             }
712 
713             dn.remove( dn.size() - 1 );
714         }
715 
716         return dn;
717     }
718 
719 
720     public LdapDN getSuffixDn()
721     {
722         return LdapDN.EMPTY_LDAPDN;
723     }
724 
725     public LdapDN getUpSuffixDn()
726     {
727         return LdapDN.EMPTY_LDAPDN;
728     }
729 
730 
731     /**
732      * @see PartitionNexus#getSuffix( GetSuffixOperationContext )
733      */
734     public LdapDN getSuffix ( GetSuffixOperationContext getSuffixContext ) throws Exception
735     {
736         Partition backend = getPartition( getSuffixContext.getDn() );
737         return backend.getSuffixDn();
738     }
739 
740 
741     /**
742      * @see PartitionNexus#listSuffixes( ListSuffixOperationContext )
743      */
744     public Iterator<String> listSuffixes ( ListSuffixOperationContext emptyContext ) throws Exception
745     {
746         return Collections.unmodifiableSet( partitions.keySet() ).iterator();
747     }
748 
749 
750     public ClonedServerEntry getRootDSE( GetRootDSEOperationContext getRootDSEContext )
751     {
752         return new ClonedServerEntry( rootDSE );
753     }
754 
755 
756     /**
757      * Unregisters an ContextPartition with this BackendManager.  Called for each
758      * registered Backend right befor it is to be stopped.  This prevents
759      * protocol server requests from reaching the Backend and effectively puts
760      * the ContextPartition's naming context offline.
761      *
762      * Operations against the naming context should result in an LDAP BUSY
763      * result code in the returnValue if the naming context is not online.
764      *
765      * @param partition ContextPartition component to unregister with this
766      * BackendNexus.
767      * @throws Exception if there are problems unregistering the partition
768      */
769     private void unregister( Partition partition ) throws Exception
770     {
771         EntryAttribute namingContexts = rootDSE.get( SchemaConstants.NAMING_CONTEXTS_AT );
772         
773         if ( namingContexts != null )
774         {
775             namingContexts.remove( partition.getSuffixDn().getUpName() );
776         }
777         
778         partitions.remove( partition.getSuffixDn().toString() );
779     }
780 
781 
782     // ------------------------------------------------------------------------
783     // DirectoryPartition Interface Method Implementations
784     // ------------------------------------------------------------------------
785     public void bind( BindOperationContext bindContext ) throws Exception
786     {
787         Partition partition = getPartition( bindContext.getDn() );
788         partition.bind( bindContext );
789     }
790 
791     public void unbind( UnbindOperationContext unbindContext ) throws Exception
792     {
793         Partition partition = getPartition( unbindContext.getDn() );
794         partition.unbind( unbindContext );
795     }
796 
797 
798     /**
799      * @see Partition#delete(DeleteOperationContext)
800      */
801     public void delete( DeleteOperationContext deleteContext ) throws Exception
802     {
803         Partition backend = getPartition( deleteContext.getDn() );
804         backend.delete( deleteContext );
805     }
806 
807 
808     /**
809      * Looks up the backend corresponding to the entry first, then checks to
810      * see if the entry already exists.  If so an exception is thrown.  If not
811      * the add operation against the backend proceeds.  This check is performed
812      * here so backend implementors do not have to worry about performing these
813      * kinds of checks.
814      *
815      * @see Partition#add( AddOperationContext )
816      */
817     public void add( AddOperationContext addContext ) throws Exception
818     {
819         Partition backend = getPartition( addContext.getDn() );
820         backend.add( addContext );
821     }
822 
823 
824     public void modify( ModifyOperationContext modifyContext ) throws Exception
825     {
826         Partition backend = getPartition( modifyContext.getDn() );
827         backend.modify( modifyContext );
828     }
829 
830 
831     /**
832      * @see Partition#list(ListOperationContext)
833      */
834     public EntryFilteringCursor list( ListOperationContext opContext ) throws Exception
835     {
836         Partition backend = getPartition( opContext.getDn() );
837         return backend.list( opContext );
838     }
839 
840 
841     public EntryFilteringCursor search( SearchOperationContext opContext )
842         throws Exception
843     {
844         LdapDN base = opContext.getDn();
845         SearchControls searchCtls = opContext.getSearchControls();
846         ExprNode filter = opContext.getFilter();
847         
848         // TODO since we're handling the *, and + in the EntryFilteringCursor
849         // we may not need this code: we need see if this is actually the 
850         // case and remove this code.
851         if ( base.size() == 0 )
852         {
853             boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
854             
855             // test for (objectClass=*)
856             boolean isSearchAll = false;
857             
858             // We have to be careful, as we may have a filter which is not a PresenceFilter
859             if ( filter instanceof PresenceNode )
860             {
861                 isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equals( SchemaConstants.OBJECT_CLASS_AT_OID );
862             }
863 
864             /*
865              * if basedn is "", filter is "(objectclass=*)" and scope is object
866              * then we have a request for the rootDSE
867              */
868             if ( filter instanceof PresenceNode && isObjectScope && isSearchAll )
869             {
870                 String[] ids = searchCtls.getReturningAttributes();
871 
872                 // -----------------------------------------------------------
873                 // If nothing is asked for then we just return the entry asis.
874                 // We let other mechanisms filter out operational attributes.
875                 // -----------------------------------------------------------
876                 if ( ( ids == null ) || ( ids.length == 0 ) )
877                 {
878                     ServerEntry rootDSE = (ServerEntry)getRootDSE( null ).clone();
879                     return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( rootDSE ), opContext );
880                 }
881                 
882                 // -----------------------------------------------------------
883                 // Collect all the real attributes besides 1.1, +, and * and
884                 // note if we've seen these special attributes as well.
885                 // -----------------------------------------------------------
886 
887                 Set<String> realIds = new HashSet<String>();
888                 boolean containsAsterisk = false;
889                 boolean containsPlus = false;
890                 boolean containsOneDotOne = false;
891                 
892                 for ( String id:ids )
893                 {
894                     String idTrimmed = id.trim();
895                     
896                     if ( idTrimmed.equals( SchemaConstants.ALL_USER_ATTRIBUTES ) )
897                     {
898                         containsAsterisk = true;
899                     }
900                     else if ( idTrimmed.equals( SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES ) )
901                     {
902                         containsPlus = true;
903                     }
904                     else if ( idTrimmed.equals( SchemaConstants.NO_ATTRIBUTE ) )
905                     {
906                         containsOneDotOne = true;
907                     }
908                     else
909                     {
910                         try
911                         {
912                             realIds.add( oidRegistry.getOid( idTrimmed ) );
913                         }
914                         catch ( Exception e )
915                         {
916                             realIds.add( idTrimmed );
917                         }
918                     }
919                 }
920 
921                 // return nothing
922                 if ( containsOneDotOne )
923                 {
924                     ServerEntry serverEntry = new DefaultServerEntry( registries, base );
925                     return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), opContext );
926                 }
927                 
928                 // return everything
929                 if ( containsAsterisk && containsPlus )
930                 {
931                     ServerEntry rootDSE = (ServerEntry)getRootDSE( null ).clone();
932                     return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( rootDSE ), opContext );
933                 }
934                 
935                 ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() );
936                 
937                 ServerEntry rootDSE = getRootDSE( new GetRootDSEOperationContext( opContext.getSession() ) );
938                 
939                 for ( EntryAttribute attribute:rootDSE )
940                 {
941                     AttributeType type = atRegistry.lookup( attribute.getUpId() );
942                     
943                     if ( realIds.contains( type.getOid() ) )
944                     {
945                         serverEntry.put( attribute );
946                     }
947                     else if ( containsAsterisk && ( type.getUsage() == UsageEnum.USER_APPLICATIONS ) )
948                     {
949                         serverEntry.put( attribute );
950                     }
951                     else if ( containsPlus && ( type.getUsage() != UsageEnum.USER_APPLICATIONS ) )
952                     {
953                         serverEntry.put( attribute );
954                     }
955                 }
956 
957                 return new BaseEntryFilteringCursor( new SingletonCursor<ServerEntry>( serverEntry ), opContext );
958             }
959 
960             // TODO : handle searches based on the RootDSE
961             throw new LdapNameNotFoundException();
962         }
963 
964         Partition backend = getPartition( base );
965         return backend.search( opContext );
966     }
967 
968 
969     public ClonedServerEntry lookup( LookupOperationContext opContext ) throws Exception
970     {
971         LdapDN dn = opContext.getDn();
972         
973         if ( dn.size() == 0 )
974         {
975             ClonedServerEntry retval = new ClonedServerEntry( rootDSE );
976             Set<AttributeType> attributeTypes = rootDSE.getAttributeTypes();
977      
978             if ( opContext.getAttrsId() != null && ! opContext.getAttrsId().isEmpty() )
979             {
980                 for ( AttributeType attributeType:attributeTypes )
981                 {
982                     String oid = attributeType.getOid();
983                     
984                     if ( ! opContext.getAttrsId().contains( oid ) )
985                     {
986                         retval.removeAttributes( attributeType );
987                     }
988                 }
989                 return retval;
990             }
991             else
992             {
993                 return new ClonedServerEntry( rootDSE );
994             }
995         }
996 
997         Partition backend = getPartition( dn );
998         return backend.lookup( opContext );
999     }
1000 
1001 
1002     /**
1003      * @see Partition#hasEntry(EntryOperationContext)
1004      */
1005     public boolean hasEntry( EntryOperationContext opContext ) throws Exception
1006     {
1007         LdapDN dn = opContext.getDn();
1008         
1009         if ( IS_DEBUG )
1010         {
1011             LOG.debug( "Check if DN '" + dn + "' exists." );
1012         }
1013 
1014         if ( dn.size() == 0 )
1015         {
1016             return true;
1017         }
1018 
1019         Partition backend = getPartition( dn );
1020         return backend.hasEntry( opContext );
1021     }
1022 
1023 
1024     /**
1025      * @see Partition#rename(RenameOperationContext)
1026      */
1027     public void rename( RenameOperationContext opContext ) throws Exception
1028     {
1029         Partition backend = getPartition( opContext.getDn() );
1030         backend.rename( opContext );
1031     }
1032 
1033 
1034     /**
1035      * @see Partition#move(MoveOperationContext)
1036      */
1037     public void move( MoveOperationContext opContext ) throws Exception
1038     {
1039         Partition backend = getPartition( opContext.getDn() );
1040         backend.move( opContext );
1041     }
1042 
1043 
1044     public void moveAndRename( MoveAndRenameOperationContext opContext ) throws Exception
1045     {
1046         Partition backend = getPartition( opContext.getDn() );
1047         backend.moveAndRename( opContext );
1048     }
1049 
1050 
1051     /**
1052      * Gets the partition associated with a normalized dn.
1053      *
1054      * @param dn the normalized distinguished name to resolve to a partition
1055      * @return the backend partition associated with the normalized dn
1056      * @throws Exception if the name cannot be resolved to a partition
1057      */
1058     public Partition getPartition( LdapDN dn ) throws Exception
1059     {
1060         Enumeration<String> rdns = dn.getAll();
1061         
1062         // This is synchronized so that we can't read the
1063         // partitionList when it is modified.
1064         synchronized ( partitionLookupTree )
1065         {
1066             Node currentNode = partitionLookupTree;
1067 
1068             // Iterate through all the RDN until we find the associated partition
1069             while ( rdns.hasMoreElements() )
1070             {
1071                 String rdn = rdns.nextElement();
1072 
1073                 if ( currentNode == null )
1074                 {
1075                     break;
1076                 }
1077 
1078                 if ( currentNode instanceof LeafNode )
1079                 {
1080                     return ( ( LeafNode ) currentNode ).getPartition();
1081                 }
1082 
1083                 BranchNode currentBranch = ( BranchNode ) currentNode;
1084                 
1085                 if ( currentBranch.contains( rdn ) )
1086                 {
1087                     currentNode = currentBranch.getChild( rdn );
1088                     
1089                     if ( currentNode instanceof LeafNode )
1090                     {
1091                         return ( ( LeafNode ) currentNode ).getPartition();
1092                     }
1093                 }
1094             }
1095         }
1096         
1097         throw new LdapNameNotFoundException( dn.getUpName() );
1098     }
1099 
1100 
1101     public void registerSupportedExtensions( Set<String> extensionOids ) throws Exception
1102     {
1103         EntryAttribute supportedExtension = rootDSE.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1104 
1105         if ( supportedExtension == null )
1106         {
1107             rootDSE.set( SchemaConstants.SUPPORTED_EXTENSION_AT );
1108             supportedExtension = rootDSE.get( SchemaConstants.SUPPORTED_EXTENSION_AT );
1109         }
1110 
1111         for ( String extensionOid : extensionOids )
1112         {
1113             supportedExtension.add( extensionOid );
1114         }
1115     }
1116 
1117 
1118     public void registerSupportedSaslMechanisms( Set<String> supportedSaslMechanisms ) throws Exception
1119     {
1120         EntryAttribute supportedSaslMechanismsAttribute = rootDSE.get( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1121 
1122         if ( supportedSaslMechanismsAttribute == null )
1123         {
1124             rootDSE.set( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1125             supportedSaslMechanismsAttribute = rootDSE.get( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT );
1126         }
1127 
1128         for ( String saslMechanism : supportedSaslMechanisms )
1129         {
1130             supportedSaslMechanismsAttribute.add( saslMechanism );
1131         }
1132     }
1133 
1134 
1135     public ClonedServerEntry lookup( Long id ) throws Exception
1136     {
1137         // TODO not implemented until we can use id to figure out the partition using
1138         // the partition ID component of the 64 bit Long identifier
1139         throw new NotImplementedException();
1140     }
1141 }