1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.ldap.server.partition;
18
19
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import javax.naming.ConfigurationException;
30 import javax.naming.Name;
31 import javax.naming.NameNotFoundException;
32 import javax.naming.NamingEnumeration;
33 import javax.naming.NamingException;
34 import javax.naming.directory.Attribute;
35 import javax.naming.directory.Attributes;
36 import javax.naming.directory.BasicAttribute;
37 import javax.naming.directory.BasicAttributes;
38 import javax.naming.directory.ModificationItem;
39 import javax.naming.directory.SearchControls;
40 import javax.naming.directory.SearchResult;
41 import javax.naming.ldap.LdapContext;
42
43 import org.apache.ldap.common.MultiException;
44 import org.apache.ldap.common.NotImplementedException;
45 import org.apache.ldap.common.schema.AttributeType;
46 import org.apache.ldap.common.schema.Normalizer;
47 import org.apache.ldap.common.exception.LdapNameNotFoundException;
48 import org.apache.ldap.common.exception.LdapInvalidAttributeIdentifierException;
49 import org.apache.ldap.common.exception.LdapNoSuchAttributeException;
50 import org.apache.ldap.common.filter.ExprNode;
51 import org.apache.ldap.common.filter.PresenceNode;
52 import org.apache.ldap.common.message.LockableAttributeImpl;
53 import org.apache.ldap.common.message.LockableAttributes;
54 import org.apache.ldap.common.message.LockableAttributesImpl;
55 import org.apache.ldap.common.name.LdapName;
56 import org.apache.ldap.common.util.DateUtils;
57 import org.apache.ldap.common.util.NamespaceTools;
58 import org.apache.ldap.common.util.SingletonEnumeration;
59 import org.apache.ldap.server.DirectoryServiceConfiguration;
60 import org.apache.ldap.server.configuration.DirectoryPartitionConfiguration;
61 import org.apache.ldap.server.configuration.MutableDirectoryPartitionConfiguration;
62 import org.apache.ldap.server.partition.impl.btree.jdbm.JdbmDirectoryPartition;
63 import org.apache.ldap.server.schema.AttributeTypeRegistry;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67
68 /***
69 * A nexus for partitions dedicated for storing entries specific to a naming
70 * context.
71 *
72 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
73 * @version $Rev: 326212 $
74 */
75 public class DefaultDirectoryPartitionNexus extends DirectoryPartitionNexus
76 {
77 private static final Logger log = LoggerFactory.getLogger( DefaultDirectoryPartitionNexus.class );
78
79 /*** the vendorName string proudly set to: Apache Software Foundation*/
80 private static final String ASF = "Apache Software Foundation";
81
82 /*** the vendorName DSE operational attribute */
83 private static final String VENDORNAME_ATTR = "vendorName";
84
85 /*** the vendorVersion DSE operational attribute */
86 private static final String VENDORVERSION_ATTR = "vendorVersion";
87
88 /*** the namingContexts DSE operational attribute */
89 private static final String NAMINGCTXS_ATTR = "namingContexts";
90
91 /*** the closed state of this partition */
92 private boolean initialized;
93
94 private DirectoryServiceConfiguration factoryCfg;
95
96 /*** the system backend */
97 private DirectoryPartition system;
98
99 /*** the backends keyed by normalized suffix strings */
100 private HashMap partitions = new HashMap();
101
102 /*** the read only rootDSE attributes */
103 private final Attributes rootDSE;
104
105
106 /***
107 * Creates the root nexus singleton of the entire system. The root DSE has
108 * several attributes that are injected into it besides those that may
109 * already exist. As partitions are added to the system more namingContexts
110 * attributes are added to the rootDSE.
111 *
112 * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a>
113 */
114 public DefaultDirectoryPartitionNexus( Attributes rootDSE )
115 {
116
117 this.rootDSE = rootDSE;
118 Attribute attr = new LockableAttributeImpl( "subschemaSubentry" );
119 attr.add( "cn=schema,ou=system" );
120 rootDSE.put( attr );
121
122 attr = new LockableAttributeImpl( "supportedLDAPVersion" );
123 rootDSE.put( attr );
124 attr.add( "3" );
125
126 attr = new LockableAttributeImpl( "supportedFeatures" );
127 rootDSE.put( attr );
128 attr.add( "1.3.6.1.4.1.4203.1.5.1" );
129
130 attr = new LockableAttributeImpl( "objectClass" );
131 rootDSE.put( attr );
132 attr.add( "top" );
133 attr.add( "extensibleObject" );
134
135 attr = new LockableAttributeImpl( NAMINGCTXS_ATTR );
136 rootDSE.put( attr );
137
138 attr = new LockableAttributeImpl( VENDORNAME_ATTR );
139 attr.add( ASF );
140 rootDSE.put( attr );
141
142 attr = new LockableAttributeImpl( VENDORVERSION_ATTR );
143 attr.add( "$Rev: 326212 $" );
144 rootDSE.put( attr );
145 }
146
147
148 public void init( DirectoryServiceConfiguration factoryCfg, DirectoryPartitionConfiguration cfg ) throws NamingException
149 {
150
151 if( initialized )
152 {
153 return;
154 }
155
156 this.factoryCfg = factoryCfg;
157
158 List initializedPartitionCfgs = new ArrayList();
159 initializedPartitionCfgs.add( initializeSystemPartition() );
160
161 Iterator i = factoryCfg.getStartupConfiguration().getContextPartitionConfigurations().iterator();
162 try
163 {
164 while( i.hasNext() )
165 {
166 DirectoryPartitionConfiguration c = ( DirectoryPartitionConfiguration ) i.next();
167 addContextPartition( c );
168 initializedPartitionCfgs.add( 0, c );
169 }
170 initialized = true;
171 }
172 finally
173 {
174 if( !initialized )
175 {
176 i = initializedPartitionCfgs.iterator();
177 while( i.hasNext() )
178 {
179 DirectoryPartitionConfiguration partitionCfg =
180 ( DirectoryPartitionConfiguration ) i.next();
181 DirectoryPartition partition = partitionCfg.getContextPartition();
182 i.remove();
183 try
184 {
185 partition.destroy();
186 }
187 catch( Exception e )
188 {
189 log.warn(
190 "Failed to destroy a partition: " +
191 partitionCfg.getSuffix(), e );
192 }
193 finally
194 {
195 unregister( partition );
196 }
197 }
198 }
199 }
200 }
201
202
203 private DirectoryPartitionConfiguration initializeSystemPartition() throws NamingException
204 {
205
206 MutableDirectoryPartitionConfiguration systemCfg = new MutableDirectoryPartitionConfiguration();
207 system = new JdbmDirectoryPartition();
208 systemCfg.setName( "system" );
209 systemCfg.setSuffix( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX );
210 systemCfg.setContextPartition( system );
211
212
213 Set indexedSystemAttrs = new HashSet();
214 indexedSystemAttrs.add( Oid.ALIAS );
215 indexedSystemAttrs.add( Oid.EXISTANCE );
216 indexedSystemAttrs.add( Oid.HIERARCHY );
217 indexedSystemAttrs.add( Oid.NDN );
218 indexedSystemAttrs.add( Oid.ONEALIAS );
219 indexedSystemAttrs.add( Oid.SUBALIAS );
220 indexedSystemAttrs.add( Oid.UPDN );
221 systemCfg.setIndexedAttributes( indexedSystemAttrs );
222
223
224 Attributes systemEntry = new BasicAttributes( true );
225 Attribute objectClassAttr = new BasicAttribute( "objectClass" );
226 objectClassAttr.add( "top" );
227 objectClassAttr.add( "organizationalUnit" );
228 systemEntry.put( objectClassAttr );
229 systemEntry.put( "creatorsName", DirectoryPartitionNexus.ADMIN_PRINCIPAL ) ;
230 systemEntry.put( "createTimestamp", DateUtils.getGeneralizedTime() ) ;
231 systemEntry.put(
232 NamespaceTools.getRdnAttribute( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX ),
233 NamespaceTools.getRdnValue( DirectoryPartitionNexus.SYSTEM_PARTITION_SUFFIX ) ) ;
234 systemCfg.setContextEntry( systemEntry );
235
236 system.init( factoryCfg, systemCfg );
237 String key = system.getSuffix( true ).toString();
238 if( partitions.containsKey( key ) )
239 {
240 throw new ConfigurationException( "Duplicate partition suffix: " + key );
241 }
242 partitions.put( key, system );
243
244 Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
245 namingContexts.add( system.getSuffix( false ).toString() );
246
247 return systemCfg;
248 }
249
250
251 public boolean isInitialized()
252 {
253 return initialized;
254 }
255
256
257 public synchronized void destroy()
258 {
259 if ( !initialized )
260 {
261 return;
262 }
263
264 Iterator suffixes = new HashSet( this.partitions.keySet() ).iterator();
265
266
267
268 while ( suffixes.hasNext() )
269 {
270 String suffix = (String) suffixes.next();
271 try
272 {
273 removeContextPartition( new LdapName( suffix ) );
274 }
275 catch ( NamingException e )
276 {
277 log.warn( "Failed to destroy a partition: " + suffix, e );
278 }
279 }
280
281 initialized = false;
282 }
283
284
285 /***
286 * @see DirectoryPartition#sync()
287 */
288 public void sync() throws NamingException
289 {
290 MultiException error = null;
291
292 Iterator list = this.partitions.values().iterator();
293
294 while ( list.hasNext() )
295 {
296 DirectoryPartition partition = ( DirectoryPartition ) list.next();
297
298 try
299 {
300 partition.sync();
301 }
302 catch ( NamingException e )
303 {
304 log.warn( "Failed to flush partition data out.", e );
305
306 if ( error == null )
307 {
308 error = new MultiException( "Grouping many exceptions on root nexus sync()" );
309 }
310
311
312 error.addThrowable( e );
313 }
314 }
315
316 if ( error != null )
317 {
318 String msg = "Encountered failures while performing a sync() operation on backing stores";
319
320 NamingException total = new NamingException( msg );
321
322 total.setRootCause( error );
323 }
324 }
325
326
327
328
329
330
331
332 public boolean compare( Name name, String oid, Object value ) throws NamingException
333 {
334 DirectoryPartition partition = getBackend( name );
335 AttributeTypeRegistry registry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
336
337
338 if ( ! registry.hasAttributeType( oid ) )
339 {
340 throw new LdapInvalidAttributeIdentifierException( oid + " not found within the attributeType registry" );
341 }
342
343 AttributeType attrType = registry.lookup( oid );
344 Attribute attr = partition.lookup( name ).get( attrType.getName() );
345
346
347 if ( attr == null )
348 {
349 throw new LdapNoSuchAttributeException();
350 }
351
352
353 if ( attr.contains( value ) )
354 {
355 return true;
356 }
357
358
359
360
361
362
363
364
365 Normalizer normalizer = attrType.getEquality().getNormalizer();
366 String reqVal = ( String ) normalizer.normalize( value );
367 for ( int ii = 0; ii < attr.size(); ii++ )
368 {
369 String attrVal = ( String ) normalizer.normalize( attr.get( ii ) );
370 if ( attrVal.equals( reqVal ) )
371 {
372 return true;
373 }
374 }
375
376 return false;
377 }
378
379
380 public synchronized void addContextPartition( DirectoryPartitionConfiguration config ) throws NamingException
381 {
382 DirectoryPartition partition = config.getContextPartition();
383
384
385 MutableDirectoryPartitionConfiguration mcfg =
386 new MutableDirectoryPartitionConfiguration();
387 mcfg.setName( config.getName() );
388 mcfg.setSuffix( config.getSuffix() );
389 mcfg.setContextEntry( config.getContextEntry() );
390 mcfg.setContextPartition( partition );
391
392 Set indexedAttrs = config.getIndexedAttributes();
393 indexedAttrs.add( Oid.ALIAS );
394 indexedAttrs.add( Oid.EXISTANCE );
395 indexedAttrs.add( Oid.HIERARCHY );
396 indexedAttrs.add( Oid.NDN );
397 indexedAttrs.add( Oid.ONEALIAS );
398 indexedAttrs.add( Oid.SUBALIAS );
399 indexedAttrs.add( Oid.UPDN );
400 mcfg.setIndexedAttributes( indexedAttrs );
401
402 String key = config.getSuffix();
403 if( partitions.containsKey( key ) )
404 {
405 throw new ConfigurationException( "Duplicate partition suffix: " + key );
406 }
407
408 partition.init( factoryCfg, mcfg );
409 partitions.put( partition.getSuffix( true ).toString(), partition );
410
411 Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
412 namingContexts.add( partition.getSuffix( false ).toString() );
413 }
414
415
416 public synchronized void removeContextPartition( Name suffix ) throws NamingException
417 {
418 String key = suffix.toString();
419 DirectoryPartition partition = (DirectoryPartition) partitions.get( key );
420 if( partition == null )
421 {
422 throw new NameNotFoundException( "No partition with suffix: " + key );
423 }
424
425 Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
426 namingContexts.remove( partition.getSuffix( false ).toString() );
427 partitions.remove( key );
428
429 partition.sync();
430 partition.destroy();
431 }
432
433 public DirectoryPartition getSystemPartition()
434 {
435 return system;
436 }
437
438 /***
439 * @see DirectoryPartitionNexus#getLdapContext()
440 */
441 public LdapContext getLdapContext()
442 {
443 throw new NotImplementedException();
444 }
445
446
447 /***
448 * @see DirectoryPartitionNexus#getMatchedName(javax.naming.Name, boolean)
449 */
450 public Name getMatchedName( Name dn, boolean normalized ) throws NamingException
451 {
452 dn = ( Name ) dn.clone();
453
454 while ( dn.size() > 0 )
455 {
456 if ( hasEntry( dn ) )
457 {
458 return dn;
459 }
460
461 dn = dn.getPrefix( 1 );
462 }
463
464 return dn;
465 }
466
467
468 public Name getSuffix( boolean normalized )
469 {
470 return new LdapName();
471 }
472
473
474 /***
475 * @see org.apache.ldap.server.partition.DirectoryPartitionNexus#getSuffix(javax.naming.Name, boolean)
476 */
477 public Name getSuffix( Name dn, boolean normalized ) throws NamingException
478 {
479 DirectoryPartition backend = getBackend( dn );
480
481 return backend.getSuffix( normalized );
482 }
483
484
485 /***
486 * @see org.apache.ldap.server.partition.DirectoryPartitionNexus#listSuffixes(boolean)
487 */
488 public Iterator listSuffixes( boolean normalized ) throws NamingException
489 {
490 return Collections.unmodifiableSet( partitions.keySet() ).iterator();
491 }
492
493
494 public Attributes getRootDSE()
495 {
496 return rootDSE;
497 }
498
499
500 /***
501 * Unregisters an ContextPartition with this BackendManager. Called for each
502 * registered Backend right befor it is to be stopped. This prevents
503 * protocol server requests from reaching the Backend and effectively puts
504 * the ContextPartition's naming context offline.
505 *
506 * Operations against the naming context should result in an LDAP BUSY
507 * result code in the returnValue if the naming context is not online.
508 *
509 * @param partition ContextPartition component to unregister with this
510 * BackendNexus.
511 */
512 private void unregister( DirectoryPartition partition ) throws NamingException
513 {
514 Attribute namingContexts = rootDSE.get( NAMINGCTXS_ATTR );
515 namingContexts.remove( partition.getSuffix( false ).toString() );
516 partitions.remove( partition.getSuffix( true ).toString() );
517 }
518
519
520
521
522
523
524
525 /***
526 * @see DirectoryPartition#delete(javax.naming.Name)
527 */
528 public void delete( Name dn ) throws NamingException
529 {
530 DirectoryPartition backend = getBackend( dn );
531
532 backend.delete( dn );
533 }
534
535
536 /***
537 * Looks up the backend corresponding to the entry first, then checks to
538 * see if the entry already exists. If so an exception is thrown. If not
539 * the add operation against the backend proceeds. This check is performed
540 * here so backend implementors do not have to worry about performing these
541 * kinds of checks.
542 *
543 * @see org.apache.ldap.server.partition.DirectoryPartition#add(String, Name, Attributes)
544 */
545 public void add( String updn, Name dn, Attributes an_entry ) throws NamingException
546 {
547 DirectoryPartition backend = getBackend( dn );
548
549 backend.add( updn, dn, an_entry );
550 }
551
552
553 /***
554 * @see DirectoryPartition#modify(Name, int,Attributes)
555 */
556 public void modify( Name dn, int modOp, Attributes mods ) throws NamingException
557 {
558 DirectoryPartition backend = getBackend( dn );
559
560 backend.modify( dn, modOp, mods );
561 }
562
563
564 /***
565 * @see DirectoryPartition#modify(javax.naming.Name,
566 * javax.naming.directory.ModificationItem[])
567 */
568 public void modify( Name dn, ModificationItem[] mods ) throws NamingException
569 {
570 DirectoryPartition backend = getBackend( dn );
571
572 backend.modify( dn, mods );
573 }
574
575
576 /***
577 * @see DirectoryPartition#list(javax.naming.Name)
578 */
579 public NamingEnumeration list( Name base ) throws NamingException
580 {
581 DirectoryPartition backend = getBackend( base );
582
583 return backend.list( base );
584 }
585
586
587 /***
588 * @see DirectoryPartition#search(Name, Map, ExprNode, SearchControls)
589 */
590 public NamingEnumeration search( Name base, Map env, ExprNode filter, SearchControls searchCtls )
591 throws NamingException
592 {
593
594 if ( base.size() == 0 )
595 {
596 boolean isObjectScope = searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
597
598 boolean isSearchAll = ( ( PresenceNode ) filter ).getAttribute().equalsIgnoreCase( "objectclass" );
599
600
601
602
603
604 if ( filter instanceof PresenceNode && isObjectScope && isSearchAll )
605 {
606 Attributes attrs = ( Attributes ) getRootDSE().clone();
607
608 String[] ids = searchCtls.getReturningAttributes();
609
610 if ( ids != null && ids.length > 0 )
611 {
612 boolean doSwap = true;
613
614 Attributes askedFor = new LockableAttributesImpl();
615
616 for ( int ii = 0; ii < ids.length; ii++ )
617 {
618 if ( ids[ii].trim().equals( "*" ) )
619 {
620 doSwap = false;
621
622 break;
623 }
624
625 if ( attrs.get( ids[ii] ) != null )
626 {
627 askedFor.put( attrs.get( ids[ii] ) );
628 }
629 }
630
631 if ( doSwap )
632 {
633 attrs = askedFor;
634 }
635 }
636
637 SearchResult result = new SearchResult( "", null, attrs, false );
638
639 return new SingletonEnumeration( result );
640 }
641
642 throw new LdapNameNotFoundException();
643 }
644
645 DirectoryPartition backend = getBackend( base );
646
647 return backend.search( base, env, filter, searchCtls );
648 }
649
650
651 /***
652 * @see DirectoryPartition#lookup(javax.naming.Name)
653 */
654 public Attributes lookup( Name dn ) throws NamingException
655 {
656 if ( dn.size() == 0 )
657 {
658 LockableAttributes retval = ( LockableAttributes ) rootDSE.clone();
659
660 retval.setLocked( true );
661
662 return retval;
663 }
664
665 DirectoryPartition backend = getBackend( dn );
666
667 return backend.lookup( dn );
668 }
669
670
671 /***
672 * @see org.apache.ldap.server.partition.DirectoryPartition#lookup(javax.naming.Name, String[])
673 */
674 public Attributes lookup( Name dn, String[] attrIds ) throws NamingException
675 {
676 if ( dn.size() == 0 )
677 {
678 LockableAttributes retval = new LockableAttributesImpl();
679
680 NamingEnumeration list = rootDSE.getIDs();
681
682 while ( list.hasMore() )
683 {
684 String id = ( String ) list.next();
685
686 Attribute attr = rootDSE.get( id );
687
688 retval.put( ( Attribute ) attr.clone() );
689 }
690
691 retval.setLocked( true );
692
693 return retval;
694 }
695
696 DirectoryPartition backend = getBackend( dn );
697
698 return backend.lookup( dn, attrIds );
699 }
700
701
702 /***
703 * @see DirectoryPartition#hasEntry(javax.naming.Name)
704 */
705 public boolean hasEntry( Name dn ) throws NamingException
706 {
707 if ( log.isDebugEnabled() )
708 {
709 log.debug( "Check if DN '" + dn + "' exists." );
710 }
711
712 if ( dn.size() == 0 )
713 {
714 return true;
715 }
716
717 DirectoryPartition backend = getBackend( dn );
718
719 return backend.hasEntry( dn );
720 }
721
722
723 /***
724 * @see DirectoryPartition#isSuffix(javax.naming.Name)
725 */
726 public boolean isSuffix( Name dn )
727 {
728 return partitions.containsKey( dn.toString() );
729 }
730
731
732 /***
733 * @see DirectoryPartition#modifyRn(Name, String, boolean)
734 */
735 public void modifyRn( Name dn, String newRdn, boolean deleteOldRdn ) throws NamingException
736 {
737 DirectoryPartition backend = getBackend( dn );
738
739 backend.modifyRn( dn, newRdn, deleteOldRdn );
740 }
741
742
743 /***
744 * @see DirectoryPartition#move(Name, Name)
745 */
746 public void move( Name oriChildName, Name newParentName ) throws NamingException
747 {
748 DirectoryPartition backend = getBackend( oriChildName );
749
750 backend.move( oriChildName, newParentName );
751 }
752
753
754 /***
755 * @see DirectoryPartition#move(javax.naming.Name,
756 * javax.naming.Name, java.lang.String, boolean)
757 */
758 public void move( Name oldChildDn, Name newParentDn, String newRdn,
759 boolean deleteOldRdn ) throws NamingException
760 {
761 DirectoryPartition backend = getBackend( oldChildDn );
762
763 backend.move( oldChildDn, newParentDn, newRdn, deleteOldRdn );
764 }
765
766
767
768
769
770
771
772 /***
773 * Gets the backend partition associated with a normalized dn.
774 *
775 * @param dn the normalized distinguished name to resolve to a backend
776 * @return the backend partition associated with the normalized dn
777 * @throws NamingException if the name cannot be resolved to a backend
778 */
779 private DirectoryPartition getBackend( Name dn ) throws NamingException
780 {
781 Name clonedDn = ( Name ) dn.clone();
782
783 while ( clonedDn.size() > 0 )
784 {
785 if ( partitions.containsKey( clonedDn.toString() ) )
786 {
787 return ( DirectoryPartition ) partitions.get( clonedDn.toString() );
788 }
789
790 clonedDn.remove( clonedDn.size() - 1 );
791 }
792
793 throw new NameNotFoundException( dn.toString() );
794 }
795 }