001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.server.core.shared.partition; 021 022 023import java.io.InputStream; 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.HashMap; 028import java.util.HashSet; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032import java.util.Properties; 033import java.util.Set; 034 035import org.apache.directory.api.ldap.model.constants.SchemaConstants; 036import org.apache.directory.api.ldap.model.cursor.CursorException; 037import org.apache.directory.api.ldap.model.cursor.EmptyCursor; 038import org.apache.directory.api.ldap.model.cursor.SingletonCursor; 039import org.apache.directory.api.ldap.model.entry.Attribute; 040import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 041import org.apache.directory.api.ldap.model.entry.DefaultEntry; 042import org.apache.directory.api.ldap.model.entry.DefaultModification; 043import org.apache.directory.api.ldap.model.entry.Entry; 044import org.apache.directory.api.ldap.model.entry.Modification; 045import org.apache.directory.api.ldap.model.entry.ModificationOperation; 046import org.apache.directory.api.ldap.model.entry.Value; 047import org.apache.directory.api.ldap.model.exception.LdapException; 048import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException; 049import org.apache.directory.api.ldap.model.exception.LdapNoSuchObjectException; 050import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; 051import org.apache.directory.api.ldap.model.exception.LdapOtherException; 052import org.apache.directory.api.ldap.model.filter.ExprNode; 053import org.apache.directory.api.ldap.model.filter.ObjectClassNode; 054import org.apache.directory.api.ldap.model.message.SearchScope; 055import org.apache.directory.api.ldap.model.message.extended.NoticeOfDisconnect; 056import org.apache.directory.api.ldap.model.name.Dn; 057import org.apache.directory.api.ldap.model.schema.AttributeType; 058import org.apache.directory.api.ldap.model.schema.AttributeTypeOptions; 059import org.apache.directory.api.ldap.model.schema.UsageEnum; 060import org.apache.directory.api.ldap.util.tree.DnNode; 061import org.apache.directory.api.util.exception.MultiException; 062import org.apache.directory.server.constants.ServerDNConstants; 063import org.apache.directory.server.core.api.DirectoryService; 064import org.apache.directory.server.core.api.InterceptorEnum; 065import org.apache.directory.server.core.api.entry.ClonedServerEntry; 066import org.apache.directory.server.core.api.filtering.CursorList; 067import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 068import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl; 069import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 070import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext; 071import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 072import org.apache.directory.server.core.api.interceptor.context.GetRootDseOperationContext; 073import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext; 074import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 075import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 076import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 077import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 078import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 079import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 080import org.apache.directory.server.core.api.interceptor.context.UnbindOperationContext; 081import org.apache.directory.server.core.api.partition.AbstractPartition; 082import org.apache.directory.server.core.api.partition.Partition; 083import org.apache.directory.server.core.api.partition.PartitionNexus; 084import org.apache.directory.server.core.api.partition.PartitionReadTxn; 085import org.apache.directory.server.core.api.partition.PartitionTxn; 086import org.apache.directory.server.core.api.partition.PartitionWriteTxn; 087import org.apache.directory.server.core.api.partition.Subordinates; 088import org.apache.directory.server.i18n.I18n; 089import org.slf4j.Logger; 090import org.slf4j.LoggerFactory; 091 092 093/** 094 * A root {@link Partition} that contains all other partitions, and 095 * routes all operations to the child partition that matches to its base suffixes. 096 * It also provides some extended operations such as accessing rootDSE and 097 * listing base suffixes. 098 * 099 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 100 */ 101public class DefaultPartitionNexus extends AbstractPartition implements PartitionNexus 102{ 103 /** A logger for this class */ 104 private static final Logger LOG = LoggerFactory.getLogger( DefaultPartitionNexus.class ); 105 106 /** the fixed id: 'NEXUS' */ 107 private static final String NEXUS_ID = "NEXUS"; 108 109 /** Speedup for logs */ 110 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 111 112 /** the vendorName string proudly set to: Apache Software Foundation*/ 113 private static final String ASF = "Apache Software Foundation"; 114 115 /** the read only rootDSE attributes */ 116 private final Entry rootDse; 117 118 /** The DirectoryService instance */ 119 private DirectoryService directoryService; 120 121 /** the partitions keyed by normalized suffix strings */ 122 private Map<String, Partition> partitions = new HashMap<>(); 123 124 /** A structure to hold all the partitions */ 125 private DnNode<Partition> partitionLookupTree = new DnNode<>(); 126 127 private final List<Modification> mods = new ArrayList<>( 2 ); 128 129 /** The cn=schema Dn */ 130 private Dn subschemaSubentryDn; 131 132 133 /** 134 * Creates the root nexus singleton of the entire system. The root DSE has 135 * several attributes that are injected into it besides those that may 136 * already exist. As partitions are added to the system more namingContexts 137 * attributes are added to the rootDSE. 138 * 139 * @see <a href="http://www.faqs.org/rfcs/rfc3045.html">Vendor Information</a> 140 * @param rootDse the root entry for the DSA 141 * @throws LdapException on failure to initialize 142 */ 143 public DefaultPartitionNexus( Entry rootDse ) throws LdapException 144 { 145 id = NEXUS_ID; 146 suffixDn = null; 147 148 // setup that root DSE 149 this.rootDse = rootDse; 150 151 // Add the basic informations 152 rootDse.put( SchemaConstants.SUBSCHEMA_SUBENTRY_AT, ServerDNConstants.CN_SCHEMA_DN ); 153 rootDse.put( SchemaConstants.SUPPORTED_LDAP_VERSION_AT, "3" ); 154 rootDse.put( SchemaConstants.SUPPORTED_FEATURES_AT, 155 SchemaConstants.FEATURE_ALL_OPERATIONAL_ATTRIBUTES, 156 SchemaConstants.FEATURE_MODIFY_INCREMENT ); 157 rootDse.put( SchemaConstants.SUPPORTED_EXTENSION_AT, NoticeOfDisconnect.EXTENSION_OID ); 158 159 // Add the objectClasses 160 rootDse.put( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.TOP_OC, SchemaConstants.EXTENSIBLE_OBJECT_OC ); 161 162 // Add the 'vendor' name and version infos 163 rootDse.put( SchemaConstants.VENDOR_NAME_AT, ASF ); 164 165 Properties props = new Properties(); 166 167 try ( InputStream inputStream = getClass().getResourceAsStream( "version.properties" ) ) 168 { 169 props.load( inputStream ); 170 } 171 catch ( IOException e ) 172 { 173 LOG.error( I18n.err( I18n.ERR_33 ) ); 174 } 175 176 rootDse.put( SchemaConstants.VENDOR_VERSION_AT, props.getProperty( "apacheds.version", "UNKNOWN" ) ); 177 178 // The rootDSE uuid has been randomly created 179 rootDse.put( SchemaConstants.ENTRY_UUID_AT, "f290425c-8272-4e62-8a67-92b06f38dbf5" ); 180 } 181 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public void repair() throws LdapException 188 { 189 // Nothing to do 190 } 191 192 193 /** 194 * {@inheritDoc} 195 */ 196 @Override 197 protected void doRepair() throws LdapException 198 { 199 // Nothing to do 200 } 201 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 protected void doInit() throws LdapException 208 { 209 // NOTE: We ignore ContextPartitionConfiguration parameter here. 210 if ( !initialized ) 211 { 212 // Add the supported request controls 213 Iterator<String> ctrlOidItr = directoryService.getLdapCodecService().registeredRequestControls(); 214 215 while ( ctrlOidItr.hasNext() ) 216 { 217 rootDse.add( SchemaConstants.SUPPORTED_CONTROL_AT, ctrlOidItr.next() ); 218 } 219 220 // Add the supported response controls 221 ctrlOidItr = directoryService.getLdapCodecService().registeredResponseControls(); 222 223 while ( ctrlOidItr.hasNext() ) 224 { 225 rootDse.add( SchemaConstants.SUPPORTED_CONTROL_AT, ctrlOidItr.next() ); 226 } 227 228 schemaManager = directoryService.getSchemaManager(); 229 230 Value attr = rootDse.get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); 231 subschemaSubentryDn = directoryService.getDnFactory().create( attr.getString() ); 232 233 List<Partition> initializedPartitions = new ArrayList<>(); 234 235 initializedPartitions.add( 0, directoryService.getSystemPartition() ); 236 addContextPartition( directoryService.getSystemPartition() ); 237 238 try 239 { 240 for ( Partition partition : directoryService.getPartitions() ) 241 { 242 addContextPartition( partition ); 243 initializedPartitions.add( partition ); 244 } 245 246 createContextCsnModList(); 247 248 initialized = true; 249 } 250 finally 251 { 252 if ( !initialized ) 253 { 254 Iterator<Partition> i = initializedPartitions.iterator(); 255 256 while ( i.hasNext() ) 257 { 258 Partition partition = i.next(); 259 i.remove(); 260 261 try 262 { 263 partition.destroy( partition.beginReadTransaction() ); 264 } 265 catch ( Exception e ) 266 { 267 LOG.warn( "Failed to destroy a partition: " + partition.getSuffixDn(), e ); 268 } 269 finally 270 { 271 unregister( partition ); 272 } 273 } 274 } 275 } 276 } 277 } 278 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override 284 protected synchronized void doDestroy( PartitionTxn partitionTxn ) 285 { 286 if ( !initialized ) 287 { 288 return; 289 } 290 291 // make sure this loop is not fail fast so all backing stores can 292 // have an attempt at closing down and synching their cached entries 293 for ( String suffix : new HashSet<>( this.partitions.keySet() ) ) 294 { 295 try 296 { 297 removeContextPartition( suffix ); 298 } 299 catch ( Exception e ) 300 { 301 LOG.warn( "Failed to destroy a partition: " + suffixDn, e ); 302 } 303 } 304 305 initialized = false; 306 } 307 308 309 /** 310 * {@inheritDoc} 311 */ 312 @Override 313 public void setId( String id ) 314 { 315 throw new UnsupportedOperationException( I18n.err( I18n.ERR_264 ) ); 316 } 317 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override 323 public void setSuffixDn( Dn suffix ) 324 { 325 throw new UnsupportedOperationException(); 326 } 327 328 329 /** 330 * {@inheritDoc} 331 */ 332 @Override 333 public void sync() throws LdapException 334 { 335 MultiException errors = null; 336 337 for ( Partition partition : this.partitions.values() ) 338 { 339 try 340 { 341 partition.saveContextCsn( partition.beginReadTransaction() ); 342 partition.sync(); 343 } 344 catch ( Exception e ) 345 { 346 LOG.warn( "Failed to flush partition data out.", e ); 347 348 if ( errors == null ) 349 { 350 //noinspection ThrowableInstanceNeverThrown 351 errors = new MultiException( I18n.err( I18n.ERR_265 ) ); 352 } 353 354 // @todo really need to send this info to a monitor 355 errors.addThrowable( e ); 356 } 357 } 358 359 if ( errors != null ) 360 { 361 throw new LdapOtherException( errors.getMessage(), errors ); 362 } 363 } 364 365 366 // ------------------------------------------------------------------------ 367 // DirectoryPartition Interface Method Implementations 368 // ------------------------------------------------------------------------ 369 /** 370 * {@inheritDoc} 371 */ 372 @Override 373 public void add( AddOperationContext addContext ) throws LdapException 374 { 375 Partition partition = addContext.getPartition(); 376 partition.add( addContext ); 377 } 378 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override 384 public boolean compare( CompareOperationContext compareContext ) throws LdapException 385 { 386 Attribute attr = compareContext.getOriginalEntry().get( compareContext.getAttributeType() ); 387 388 // complain if the attribute being compared does not exist in the entry 389 if ( attr == null ) 390 { 391 throw new LdapNoSuchAttributeException(); 392 } 393 394 // see first if simple match without normalization succeeds 395 if ( attr.contains( compareContext.getValue() ) ) 396 { 397 return true; 398 } 399 400 // now must apply normalization to all values (attr and in request) to compare 401 402 /* 403 * Get ahold of the normalizer for the attribute and normalize the request 404 * assertion value for comparisons with normalized attribute values. Loop 405 * through all values looking for a match. 406 */ 407 Value reqVal = compareContext.getValue(); 408 409 for ( Value value : attr ) 410 { 411 if ( value.equals( reqVal ) ) 412 { 413 return true; 414 } 415 } 416 417 return false; 418 } 419 420 421 /** 422 * {@inheritDoc} 423 */ 424 @Override 425 public Entry delete( DeleteOperationContext deleteContext ) throws LdapException 426 { 427 Partition partition = getPartition( deleteContext.getDn() ); 428 return partition.delete( deleteContext ); 429 } 430 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override 436 public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException 437 { 438 Dn dn = hasEntryContext.getDn(); 439 440 if ( IS_DEBUG ) 441 { 442 LOG.debug( "Check if Dn '{}' exists.", dn ); 443 } 444 445 if ( dn.isRootDse() ) 446 { 447 return true; 448 } 449 450 Partition partition = getPartition( dn ); 451 452 return partition.hasEntry( hasEntryContext ); 453 } 454 455 456 /** 457 * {@inheritDoc} 458 */ 459 @Override 460 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 461 { 462 Dn dn = lookupContext.getDn(); 463 464 if ( dn.getNormName().equals( subschemaSubentryDn.getNormName() ) ) 465 { 466 return new ClonedServerEntry( rootDse.clone() ); 467 } 468 469 // This is for the case we do a lookup on the rootDSE 470 if ( dn.isRootDse() ) 471 { 472 return new ClonedServerEntry( rootDse ); 473 } 474 475 Partition partition = getPartition( dn ); 476 Entry entry = partition.lookup( lookupContext ); 477 478 if ( entry == null ) 479 { 480 throw new LdapNoSuchObjectException( "Attempt to lookup non-existant entry: " 481 + dn.getName() ); 482 } 483 484 return entry; 485 } 486 487 488 /** 489 * {@inheritDoc} 490 */ 491 @Override 492 public void modify( ModifyOperationContext modifyContext ) throws LdapException 493 { 494 // Special case : if we don't have any modification to apply, just return 495 if ( modifyContext.getModItems().isEmpty() ) 496 { 497 return; 498 } 499 500 Partition partition = getPartition( modifyContext.getDn() ); 501 502 partition.modify( modifyContext ); 503 504 if ( modifyContext.isPushToEvtInterceptor() ) 505 { 506 directoryService.getInterceptor( InterceptorEnum.EVENT_INTERCEPTOR.getName() ).modify( modifyContext ); 507 } 508 } 509 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override 515 public void move( MoveOperationContext moveContext ) throws LdapException 516 { 517 // Get the current partition 518 Partition partition = getPartition( moveContext.getDn() ); 519 520 partition.move( moveContext ); 521 } 522 523 524 /** 525 * {@inheritDoc} 526 */ 527 @Override 528 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 529 { 530 Partition partition = getPartition( moveAndRenameContext.getDn() ); 531 partition.moveAndRename( moveAndRenameContext ); 532 } 533 534 535 /** 536 * {@inheritDoc} 537 */ 538 @Override 539 public void rename( RenameOperationContext renameContext ) throws LdapException 540 { 541 Partition partition = getPartition( renameContext.getDn() ); 542 partition.rename( renameContext ); 543 } 544 545 546 private EntryFilteringCursor searchRootDse( SearchOperationContext searchContext ) throws LdapException 547 { 548 Set<AttributeTypeOptions> ids = searchContext.getReturningAttributes(); 549 550 // ----------------------------------------------------------- 551 // If nothing is asked for then we just return the entry asis. 552 // We let other mechanisms filter out operational attributes. 553 // ----------------------------------------------------------- 554 if ( ( ids == null ) || ( ids.isEmpty() ) ) 555 { 556 return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( getRootDse( null ) ), searchContext, 557 directoryService.getSchemaManager() ); 558 } 559 560 // ----------------------------------------------------------- 561 // Collect all the real attributes besides 1.1, +, and * and 562 // note if we've seen these special attributes as well. 563 // ----------------------------------------------------------- 564 565 Set<String> realIds = new HashSet<>(); 566 boolean allUserAttributes = searchContext.isAllUserAttributes(); 567 boolean allOperationalAttributes = searchContext.isAllOperationalAttributes(); 568 boolean noAttribute = searchContext.isNoAttributes(); 569 570 for ( AttributeTypeOptions id : ids ) 571 { 572 try 573 { 574 realIds.add( id.getAttributeType().getOid() ); 575 } 576 catch ( Exception e ) 577 { 578 realIds.add( id.getAttributeType().getName() ); 579 } 580 } 581 582 // return nothing 583 if ( noAttribute ) 584 { 585 Entry serverEntry = new DefaultEntry( schemaManager, Dn.ROOT_DSE ); 586 return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( serverEntry ), searchContext, 587 directoryService.getSchemaManager() ); 588 } 589 590 // return everything 591 if ( allUserAttributes && allOperationalAttributes ) 592 { 593 Entry foundRootDse = getRootDse( null ); 594 return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( foundRootDse ), searchContext, 595 directoryService.getSchemaManager() ); 596 } 597 598 Entry serverEntry = new DefaultEntry( schemaManager, Dn.ROOT_DSE ); 599 GetRootDseOperationContext getRootDseContext = new GetRootDseOperationContext( searchContext.getSession() ); 600 getRootDseContext.setPartition( searchContext.getPartition() ); 601 getRootDseContext.setTransaction( searchContext.getTransaction() ); 602 603 Entry foundRootDse = getRootDse( getRootDseContext ); 604 605 for ( Attribute attribute : foundRootDse ) 606 { 607 AttributeType type = schemaManager.lookupAttributeTypeRegistry( attribute.getId() ); 608 609 if ( realIds.contains( type.getOid() ) 610 || ( allUserAttributes && ( type.getUsage() == UsageEnum.USER_APPLICATIONS ) ) 611 || ( allOperationalAttributes && ( type.getUsage() != UsageEnum.USER_APPLICATIONS ) ) ) 612 { 613 serverEntry.put( attribute ); 614 } 615 } 616 617 return new EntryFilteringCursorImpl( new SingletonCursor<Entry>( serverEntry ), searchContext, 618 directoryService.getSchemaManager() ); 619 } 620 621 622 /** 623 * {@inheritDoc} 624 */ 625 @Override 626 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 627 { 628 Dn baseDn = searchContext.getDn(); 629 630 // TODO since we're handling the *, and + in the EntryFilteringCursor 631 // we may not need this code: we need see if this is actually the 632 // case and remove this code. 633 if ( baseDn.size() == 0 ) 634 { 635 return searchFromRoot( searchContext ); 636 } 637 638 // Not sure we need this code... 639 if ( !baseDn.isSchemaAware() ) 640 { 641 searchContext.setDn( new Dn( schemaManager, baseDn ) ); 642 } 643 644 // Normal case : do a search on the specific partition 645 Partition backend = searchContext.getPartition(); 646 647 return backend.search( searchContext ); 648 } 649 650 651 /** 652 * Do a search from the root of the DIT. We have a few use cases to consider : 653 * A) The scope is OBJECT 654 * If the filter is (ObjectClass = *), then this is a RootDSE fetch, otherwise, we just 655 * return nothing. 656 * B) The scope is ONELEVEL 657 * We just return the contextEntries of all the existing partitions 658 * C) The scope is SUBLEVEL : 659 * In this case, we have to do a search in each of the existing partition. We will get 660 * back a list of cursors and we will wrap this list in the resulting EntryFilteringCursor. 661 * 662 * @param searchContext 663 * @return 664 * @throws LdapException 665 */ 666 private EntryFilteringCursor searchFromRoot( SearchOperationContext searchContext ) 667 throws LdapException 668 { 669 ExprNode filter = searchContext.getFilter(); 670 671 // We are searching from the rootDSE. We have to distinguish three cases : 672 // 1) The scope is OBJECT : we have to return the rootDSE entry, filtered 673 // 2) The scope is ONELEVEL : we have to return all the Naming Contexts 674 boolean isObjectScope = searchContext.getScope() == SearchScope.OBJECT; 675 676 boolean isOnelevelScope = searchContext.getScope() == SearchScope.ONELEVEL; 677 678 // test for (objectClass=*) 679 boolean isSearchAll = false; 680 681 // We have to be careful, as we may have a filter which is not a PresenceFilter 682 if ( filter instanceof ObjectClassNode ) 683 { 684 isSearchAll = true; 685 } 686 687 if ( isObjectScope ) 688 { 689 if ( isSearchAll ) 690 { 691 // if basedn is "", filter is "(objectclass=*)" and scope is object 692 // then we have a request for the rootDSE 693 return searchRootDse( searchContext ); 694 } 695 else 696 { 697 // Nothing to return in this case 698 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, 699 directoryService.getSchemaManager() ); 700 } 701 } 702 else if ( isOnelevelScope ) 703 { 704 // Loop on all the partitions 705 // We will look into all the partitions, thus we create a list of cursors. 706 List<EntryFilteringCursor> cursors = new ArrayList<>(); 707 708 for ( Partition partition : partitions.values() ) 709 { 710 Dn contextDn = partition.getSuffixDn(); 711 PartitionTxn partitionTxn = partition.beginReadTransaction(); 712 HasEntryOperationContext hasEntryContext = new HasEntryOperationContext( 713 searchContext.getSession(), contextDn ); 714 hasEntryContext.setPartition( partition ); 715 hasEntryContext.setTransaction( partitionTxn ); 716 searchContext.setPartition( partition ); 717 searchContext.setTransaction( partitionTxn ); 718 719 // search only if the context entry exists 720 if ( partition.hasEntry( hasEntryContext ) ) 721 { 722 searchContext.setDn( contextDn ); 723 searchContext.setScope( SearchScope.OBJECT ); 724 cursors.add( partition.search( searchContext ) ); 725 } 726 } 727 728 return new CursorList( cursors, searchContext ); 729 } 730 else 731 { 732 // This is a SUBLEVEL search. We will do multiple searches and wrap 733 // a CursorList into the EntryFilteringCursor 734 List<EntryFilteringCursor> cursors = new ArrayList<>(); 735 736 for ( Partition partition : partitions.values() ) 737 { 738 PartitionTxn partitionTxn = partition.beginReadTransaction(); 739 Dn contextDn = partition.getSuffixDn(); 740 HasEntryOperationContext hasEntryContext = new HasEntryOperationContext( 741 searchContext.getSession(), contextDn ); 742 hasEntryContext.setPartition( partition ); 743 hasEntryContext.setTransaction( partitionTxn ); 744 searchContext.setPartition( partition ); 745 searchContext.setTransaction( partitionTxn ); 746 747 if ( partition.hasEntry( hasEntryContext ) ) 748 { 749 searchContext.setDn( contextDn ); 750 EntryFilteringCursor cursor = partition.search( searchContext ); 751 752 try 753 { 754 if ( cursor.first() ) 755 { 756 cursor.beforeFirst(); 757 cursors.add( cursor ); 758 } 759 } 760 catch ( CursorException e ) 761 { 762 // Do nothing 763 } 764 } 765 } 766 767 // don't feed the above Cursors' list to a BaseEntryFilteringCursor it is skipping the naming context entry of each partition 768 if ( cursors.isEmpty() ) 769 { 770 // No candidate, return an emtpy cursor 771 return new EntryFilteringCursorImpl( new EmptyCursor<Entry>(), searchContext, 772 directoryService.getSchemaManager() ); 773 } 774 else 775 { 776 return new CursorList( cursors, searchContext ); 777 } 778 } 779 } 780 781 782 /** 783 * {@inheritDoc} 784 */ 785 @Override 786 public void unbind( UnbindOperationContext unbindContext ) throws LdapException 787 { 788 Dn unbindContextDn = unbindContext.getDn(); 789 790 if ( !Dn.isNullOrEmpty( unbindContextDn ) ) 791 { 792 Partition partition = getPartition( unbindContext.getDn() ); 793 partition.unbind( unbindContext ); 794 } 795 } 796 797 798 /** 799 * {@inheritDoc} 800 */ 801 @Override 802 public Entry getRootDse( GetRootDseOperationContext getRootDseContext ) 803 { 804 return rootDse.clone(); 805 } 806 807 808 /** 809 * {@inheritDoc} 810 */ 811 @Override 812 public Value getRootDseValue( AttributeType attributeType ) 813 { 814 return rootDse.get( attributeType ).get(); 815 } 816 817 818 /** 819 * {@inheritDoc} 820 */ 821 @Override 822 public synchronized void addContextPartition( Partition partition ) throws LdapException 823 { 824 // Turn on default indices 825 String key = partition.getSuffixDn().getNormName(); 826 827 if ( partitions.containsKey( key ) ) 828 { 829 throw new LdapOtherException( I18n.err( I18n.ERR_263, key ) ); 830 } 831 832 if ( !partition.isInitialized() ) 833 { 834 partition.initialize(); 835 } 836 837 synchronized ( partitionLookupTree ) 838 { 839 Dn partitionSuffix = partition.getSuffixDn(); 840 841 if ( partitionSuffix == null ) 842 { 843 throw new LdapOtherException( I18n.err( I18n.ERR_267, partition.getId() ) ); 844 } 845 846 partitions.put( partitionSuffix.getNormName(), partition ); 847 partitionLookupTree.add( partition.getSuffixDn(), partition ); 848 849 Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT ); 850 851 if ( namingContexts == null ) 852 { 853 namingContexts = new DefaultAttribute( schemaManager 854 .lookupAttributeTypeRegistry( SchemaConstants.NAMING_CONTEXTS_AT ), partitionSuffix.getName() ); 855 rootDse.put( namingContexts ); 856 } 857 else 858 { 859 namingContexts.add( partitionSuffix.getName() ); 860 } 861 } 862 } 863 864 865 /** 866 * {@inheritDoc} 867 */ 868 @Override 869 public synchronized void removeContextPartition( String partitionDn ) 870 throws LdapException 871 { 872 // Retrieve this partition from the aprtition's table 873 Partition partition = partitions.get( partitionDn ); 874 875 if ( partition == null ) 876 { 877 String msg = I18n.err( I18n.ERR_34, partitionDn ); 878 LOG.error( msg ); 879 throw new LdapNoSuchObjectException( msg ); 880 } 881 882 String partitionSuffix = partition.getSuffixDn().getNormName(); 883 884 // Retrieve the namingContexts from the RootDSE : the partition 885 // suffix must be present in those namingContexts 886 Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT ); 887 888 if ( namingContexts != null ) 889 { 890 Value foundNC = null; 891 892 for ( Value namingContext : namingContexts ) 893 { 894 String normalizedNC = new Dn( schemaManager, namingContext.getString() ).getNormName(); 895 896 if ( partitionSuffix.equals( normalizedNC ) ) 897 { 898 foundNC = namingContext; 899 break; 900 } 901 } 902 903 if ( foundNC != null ) 904 { 905 namingContexts.remove( foundNC ); 906 } 907 else 908 { 909 String msg = I18n.err( I18n.ERR_35, partitionDn ); 910 LOG.error( msg ); 911 throw new LdapNoSuchObjectException( msg ); 912 } 913 } 914 915 // Update the partition tree 916 synchronized ( partitionLookupTree ) 917 { 918 partitionLookupTree.remove( partition.getSuffixDn() ); 919 } 920 921 partitions.remove( partitionDn ); 922 923 try 924 { 925 partition.destroy( partition.beginReadTransaction() ); 926 } 927 catch ( Exception e ) 928 { 929 throw new LdapOperationErrorException( e.getMessage(), e ); 930 } 931 } 932 933 934 /** 935 * {@inheritDoc} 936 */ 937 @Override 938 public Partition getPartition( Dn dn ) throws LdapException 939 { 940 Partition parent; 941 942 if ( dn == null ) 943 { 944 dn = Dn.ROOT_DSE; 945 } 946 947 if ( !dn.isSchemaAware() ) 948 { 949 dn = new Dn( schemaManager, dn ); 950 } 951 952 if ( dn.isRootDse() || dn.getNormName().equals( subschemaSubentryDn.getNormName() ) ) 953 { 954 return new RootPartition( schemaManager ); 955 } 956 957 synchronized ( partitionLookupTree ) 958 { 959 parent = partitionLookupTree.getElement( dn ); 960 } 961 962 if ( parent == null ) 963 { 964 throw new LdapNoSuchObjectException( I18n.err( I18n.ERR_268, dn ) ); 965 } 966 else 967 { 968 return parent; 969 } 970 } 971 972 973 /** 974 * {@inheritDoc} 975 */ 976 @Override 977 public Dn getSuffixDn( Dn dn ) throws LdapException 978 { 979 Partition partition = getPartition( dn ); 980 981 return partition.getSuffixDn(); 982 } 983 984 985 /* (non-Javadoc) 986 */ 987 @Override 988 public Set<String> listSuffixes() throws LdapException 989 { 990 return Collections.unmodifiableSet( partitions.keySet() ); 991 } 992 993 994 /** 995 * {@inheritDoc} 996 */ 997 @Override 998 public void registerSupportedExtensions( Set<String> extensionOids ) throws LdapException 999 { 1000 Attribute supportedExtension = rootDse.get( SchemaConstants.SUPPORTED_EXTENSION_AT ); 1001 1002 if ( supportedExtension == null ) 1003 { 1004 rootDse.put( SchemaConstants.SUPPORTED_EXTENSION_AT, ( String ) null ); 1005 supportedExtension = rootDse.get( SchemaConstants.SUPPORTED_EXTENSION_AT ); 1006 } 1007 1008 for ( String extensionOid : extensionOids ) 1009 { 1010 supportedExtension.add( extensionOid ); 1011 } 1012 } 1013 1014 1015 /** 1016 * {@inheritDoc} 1017 */ 1018 @Override 1019 public void registerSupportedSaslMechanisms( Set<String> supportedSaslMechanisms ) throws LdapException 1020 { 1021 Attribute supportedSaslMechanismsAt; 1022 1023 supportedSaslMechanismsAt = new DefaultAttribute( 1024 schemaManager.lookupAttributeTypeRegistry( SchemaConstants.SUPPORTED_SASL_MECHANISMS_AT ) ); 1025 1026 for ( String saslMechanism : supportedSaslMechanisms ) 1027 { 1028 supportedSaslMechanismsAt.add( saslMechanism ); 1029 } 1030 1031 rootDse.add( supportedSaslMechanismsAt ); 1032 } 1033 1034 1035 /** 1036 * Unregisters an ContextPartition with this BackendManager. Called for each 1037 * registered Backend right befor it is to be stopped. This prevents 1038 * protocol server requests from reaching the Backend and effectively puts 1039 * the ContextPartition's naming context offline. 1040 * 1041 * Operations against the naming context should result in an LDAP BUSY 1042 * result code in the returnValue if the naming context is not online. 1043 * 1044 * @param partition ContextPartition component to unregister with this 1045 * BackendNexus. 1046 * @throws Exception if there are problems unregistering the partition 1047 */ 1048 private void unregister( Partition partition ) 1049 { 1050 Attribute namingContexts = rootDse.get( SchemaConstants.NAMING_CONTEXTS_AT ); 1051 1052 if ( namingContexts != null ) 1053 { 1054 namingContexts.remove( partition.getSuffixDn().getName() ); 1055 } 1056 1057 partitions.remove( partition.getSuffixDn().getName() ); 1058 } 1059 1060 1061 /** 1062 * @return the directoryService 1063 */ 1064 public DirectoryService getDirectoryService() 1065 { 1066 return directoryService; 1067 } 1068 1069 1070 /** 1071 * @param directoryService the directoryService to set 1072 */ 1073 public void setDirectoryService( DirectoryService directoryService ) 1074 { 1075 this.directoryService = directoryService; 1076 } 1077 1078 1079 private void createContextCsnModList() throws LdapException 1080 { 1081 Modification contextCsnMod = new DefaultModification(); 1082 contextCsnMod.setOperation( ModificationOperation.REPLACE_ATTRIBUTE ); 1083 DefaultAttribute contextCsnAt = new DefaultAttribute( schemaManager 1084 .lookupAttributeTypeRegistry( SchemaConstants.CONTEXT_CSN_AT ) ); 1085 contextCsnMod.setAttribute( contextCsnAt ); 1086 1087 mods.add( contextCsnMod ); 1088 1089 Modification timeStampMod = new DefaultModification(); 1090 timeStampMod.setOperation( ModificationOperation.REPLACE_ATTRIBUTE ); 1091 DefaultAttribute timeStampAt = new DefaultAttribute( schemaManager 1092 .lookupAttributeTypeRegistry( SchemaConstants.MODIFY_TIMESTAMP_AT ) ); 1093 timeStampMod.setAttribute( timeStampAt ); 1094 1095 mods.add( timeStampMod ); 1096 } 1097 1098 1099 /** 1100 * {@inheritDoc} 1101 */ 1102 @Override 1103 public String getContextCsn( PartitionTxn partitionTxn ) 1104 { 1105 // nexus doesn't contain a contextCSN 1106 return null; 1107 } 1108 1109 1110 @Override 1111 public void saveContextCsn( PartitionTxn partitionTxn ) throws LdapException 1112 { 1113 } 1114 1115 1116 /** 1117 * Return the number of children and subordinates for a given entry 1118 * 1119 * @param partitionTxn The Partition transaction 1120 * @param entry The entry for which we want to find the subordinates 1121 * @return The Subordinate instance that contains the values. 1122 * @throws LdapException If we had an issue while processing the request 1123 */ 1124 @Override 1125 public Subordinates getSubordinates( PartitionTxn partitionTxn, Entry entry ) throws LdapException 1126 { 1127 return new Subordinates(); 1128 } 1129 1130 1131 @Override 1132 public PartitionReadTxn beginReadTransaction() 1133 { 1134 return new PartitionReadTxn(); 1135 } 1136 1137 1138 @Override 1139 public PartitionWriteTxn beginWriteTransaction() 1140 { 1141 return new PartitionWriteTxn(); 1142 } 1143}