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.subtree; 021 022 023import java.util.ArrayList; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Set; 027 028import javax.naming.directory.SearchControls; 029 030import org.apache.directory.api.ldap.model.constants.SchemaConstants; 031import org.apache.directory.api.ldap.model.entry.Attribute; 032import org.apache.directory.api.ldap.model.entry.DefaultAttribute; 033import org.apache.directory.api.ldap.model.entry.DefaultEntry; 034import org.apache.directory.api.ldap.model.entry.DefaultModification; 035import org.apache.directory.api.ldap.model.entry.Entry; 036import org.apache.directory.api.ldap.model.entry.Modification; 037import org.apache.directory.api.ldap.model.entry.ModificationOperation; 038import org.apache.directory.api.ldap.model.entry.Value; 039import org.apache.directory.api.ldap.model.exception.LdapException; 040import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 041import org.apache.directory.api.ldap.model.exception.LdapNoSuchAttributeException; 042import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException; 043import org.apache.directory.api.ldap.model.exception.LdapOperationException; 044import org.apache.directory.api.ldap.model.exception.LdapOtherException; 045import org.apache.directory.api.ldap.model.exception.LdapSchemaViolationException; 046import org.apache.directory.api.ldap.model.filter.EqualityNode; 047import org.apache.directory.api.ldap.model.filter.ExprNode; 048import org.apache.directory.api.ldap.model.filter.ObjectClassNode; 049import org.apache.directory.api.ldap.model.filter.PresenceNode; 050import org.apache.directory.api.ldap.model.message.AliasDerefMode; 051import org.apache.directory.api.ldap.model.message.ResultCodeEnum; 052import org.apache.directory.api.ldap.model.message.SearchScope; 053import org.apache.directory.api.ldap.model.message.controls.Subentries; 054import org.apache.directory.api.ldap.model.name.Dn; 055import org.apache.directory.api.ldap.model.schema.AttributeType; 056import org.apache.directory.api.ldap.model.subtree.AdministrativeRole; 057import org.apache.directory.api.ldap.model.subtree.Subentry; 058import org.apache.directory.api.ldap.model.subtree.SubtreeSpecification; 059import org.apache.directory.api.ldap.model.subtree.SubtreeSpecificationParser; 060import org.apache.directory.server.constants.ApacheSchemaConstants; 061import org.apache.directory.server.core.api.CoreSession; 062import org.apache.directory.server.core.api.DirectoryService; 063import org.apache.directory.server.core.api.InterceptorEnum; 064import org.apache.directory.server.core.api.entry.ClonedServerEntry; 065import org.apache.directory.server.core.api.filtering.EntryFilter; 066import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 067import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 068import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 069import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 070import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 071import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 072import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 073import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 074import org.apache.directory.server.core.api.interceptor.context.OperationContext; 075import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 076import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 077import org.apache.directory.server.core.api.partition.Partition; 078import org.apache.directory.server.core.api.partition.PartitionNexus; 079import org.apache.directory.server.core.api.subtree.SubentryCache; 080import org.apache.directory.server.core.api.subtree.SubtreeEvaluator; 081import org.apache.directory.server.i18n.I18n; 082import org.slf4j.Logger; 083import org.slf4j.LoggerFactory; 084 085 086/** 087 * The Subentry interceptor service which is responsible for filtering 088 * out subentries on search operations and injecting operational attributes 089 * 090 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 091 */ 092public class SubentryInterceptor extends BaseInterceptor 093{ 094 /** The logger for this class */ 095 private static final Logger LOG = LoggerFactory.getLogger( SubentryInterceptor.class ); 096 097 /** the subentry control OID */ 098 private static final String SUBENTRY_CONTROL = Subentries.OID; 099 100 private Value subentryOC; 101 102 /** The SubTree specification parser instance */ 103 private SubtreeSpecificationParser ssParser; 104 105 /** A reference to the nexus for direct backend operations */ 106 private PartitionNexus nexus; 107 108 /** An enum used for the entries update */ 109 private enum OperationEnum 110 { 111 ADD, 112 REMOVE, 113 REPLACE 114 } 115 116 117 /** 118 * Creates a new instance of SubentryInterceptor 119 */ 120 public SubentryInterceptor() 121 { 122 super( InterceptorEnum.SUBENTRY_INTERCEPTOR ); 123 } 124 125 //------------------------------------------------------------------------------------------- 126 // Search filter methods 127 //------------------------------------------------------------------------------------------- 128 /** 129 * SearchResultFilter used to filter out subentries based on objectClass values. 130 */ 131 private class HideSubentriesFilter implements EntryFilter 132 { 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException 138 { 139 // See if the requested entry is a subentry 140 if ( directoryService.getSubentryCache().hasSubentry( entry.getDn() ) ) 141 { 142 return false; 143 } 144 145 // see if we can use objectclass if present 146 return !entry.contains( directoryService.getAtProvider().getObjectClass(), subentryOC ); 147 } 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 @Override 154 public String toString( String tabs ) 155 { 156 return tabs + "HideSubentriesFilter"; 157 } 158 } 159 160 /** 161 * SearchResultFilter used to filter out normal entries but shows subentries based on 162 * objectClass values. 163 */ 164 private class HideEntriesFilter implements EntryFilter 165 { 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException 171 { 172 // See if the requested entry is a subentry 173 if ( directoryService.getSubentryCache().hasSubentry( entry.getDn() ) ) 174 { 175 return true; 176 } 177 178 // see if we can use objectclass if present 179 return entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ); 180 } 181 182 183 /** 184 * {@inheritDoc} 185 */ 186 @Override 187 public String toString( String tabs ) 188 { 189 return tabs + "HideEntriesFilter"; 190 } 191 } 192 193 194 //------------------------------------------------------------------------------------------- 195 // Interceptor initialization 196 //------------------------------------------------------------------------------------------- 197 /** 198 * Initialize the Subentry Interceptor 199 * 200 * @param directoryService The DirectoryService instance 201 */ 202 @Override 203 public void init( DirectoryService directoryService ) throws LdapException 204 { 205 super.init( directoryService ); 206 207 nexus = directoryService.getPartitionNexus(); 208 209 ssParser = new SubtreeSpecificationParser( schemaManager ); 210 AttributeType ocAt = directoryService.getAtProvider().getObjectClass(); 211 212 // prepare to find all subentries in all namingContexts 213 Set<String> suffixes = nexus.listSuffixes(); 214 ExprNode filter = new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.SUBENTRY_OC ) ); 215 SearchControls controls = new SearchControls(); 216 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 217 controls.setReturningAttributes( new String[] 218 { SchemaConstants.SUBTREE_SPECIFICATION_AT, SchemaConstants.OBJECT_CLASS_AT } ); 219 220 subentryOC = new Value( ocAt, SchemaConstants.SUBENTRY_OC ); 221 222 // search each namingContext for subentries 223 for ( String suffix : suffixes ) 224 { 225 CoreSession adminSession = directoryService.getAdminSession(); 226 227 Dn suffixDn = dnFactory.create( suffix ); 228 Partition partition = nexus.getPartition( suffixDn ); 229 230 SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, suffixDn, filter, 231 controls ); 232 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 233 searchOperationContext.setPartition( partition ); 234 searchOperationContext.setTransaction( partition.beginReadTransaction() ); 235 236 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 237 238 // Loop on all the found Subentries, parse the SubtreeSpecification 239 // and store the subentry in the subrentry cache 240 try 241 { 242 while ( subentries.next() ) 243 { 244 Entry subentry = subentries.get(); 245 Dn subentryDn = subentry.getDn(); 246 247 String subtree = subentry.get( directoryService.getAtProvider().getSubtreeSpecification() ) 248 .getString(); 249 SubtreeSpecification ss; 250 251 try 252 { 253 ss = ssParser.parse( subtree ); 254 } 255 catch ( Exception e ) 256 { 257 LOG.warn( "Failed while parsing subtreeSpecification for {}", subentryDn ); 258 continue; 259 } 260 261 Subentry newSubentry = new Subentry(); 262 263 newSubentry.setAdministrativeRoles( getSubentryAdminRoles( subentry ) ); 264 newSubentry.setSubtreeSpecification( ss ); 265 266 directoryService.getSubentryCache().addSubentry( subentryDn, newSubentry ); 267 } 268 } 269 catch ( Exception e ) 270 { 271 throw new LdapOperationException( e.getMessage(), e ); 272 } 273 finally 274 { 275 try 276 { 277 subentries.close(); 278 } 279 catch ( Exception e ) 280 { 281 LOG.error( I18n.err( I18n.ERR_168 ), e ); 282 } 283 } 284 } 285 } 286 287 288 //------------------------------------------------------------------------------------------- 289 // Helper methods 290 //------------------------------------------------------------------------------------------- 291 /** 292 * Return the list of AdministrativeRole for a subentry 293 */ 294 private Set<AdministrativeRole> getSubentryAdminRoles( Entry subentry ) throws LdapException 295 { 296 Set<AdministrativeRole> adminRoles = new HashSet<>(); 297 298 Attribute oc = subentry.get( directoryService.getAtProvider().getObjectClass() ); 299 300 if ( oc == null ) 301 { 302 throw new LdapSchemaViolationException( ResultCodeEnum.OBJECT_CLASS_VIOLATION, I18n.err( I18n.ERR_305 ) ); 303 } 304 305 if ( oc.contains( SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) ) 306 { 307 adminRoles.add( AdministrativeRole.AccessControlInnerArea ); 308 } 309 310 if ( oc.contains( SchemaConstants.SUBSCHEMA_OC ) ) 311 { 312 adminRoles.add( AdministrativeRole.SubSchemaSpecificArea ); 313 } 314 315 if ( oc.contains( SchemaConstants.COLLECTIVE_ATTRIBUTE_SUBENTRY_OC ) ) 316 { 317 adminRoles.add( AdministrativeRole.CollectiveAttributeSpecificArea ); 318 } 319 320 if ( oc.contains( ApacheSchemaConstants.TRIGGER_EXECUTION_SUBENTRY_OC ) ) 321 { 322 adminRoles.add( AdministrativeRole.TriggerExecutionInnerArea ); 323 } 324 325 return adminRoles; 326 } 327 328 329 /** 330 * Checks to see if subentries for the search and list operations should be 331 * made visible based on the availability of the search request control 332 * 333 * @param opContext the invocation object to use for determining subentry visibility 334 * @return true if subentries should be visible, false otherwise 335 */ 336 private boolean isSubentryVisible( OperationContext opContext ) 337 { 338 if ( !opContext.hasRequestControls() ) 339 { 340 return false; 341 } 342 343 // found the subentry request control so we return its value 344 if ( opContext.hasRequestControl( SUBENTRY_CONTROL ) ) 345 { 346 Subentries subentries = ( Subentries ) opContext.getRequestControl( SUBENTRY_CONTROL ); 347 return subentries.isVisible(); 348 } 349 350 return false; 351 } 352 353 354 /** 355 * Update all the entries under an AP adding the 356 */ 357 private void updateEntries( OperationContext opContext, OperationEnum operation, 358 Dn apDn, SubtreeSpecification ss, Dn baseDn, List<Attribute> operationalAttributes ) throws LdapException 359 { 360 ExprNode filter = ObjectClassNode.OBJECT_CLASS_NODE; // (objectClass=*) 361 SearchControls controls = new SearchControls(); 362 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 363 controls.setReturningAttributes( new String[] 364 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 365 366 SearchOperationContext searchOperationContext = new SearchOperationContext( opContext.getSession(), 367 baseDn, filter, controls ); 368 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 369 searchOperationContext.setPartition( opContext.getPartition() ); 370 searchOperationContext.setTransaction( opContext.getTransaction() ); 371 372 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 373 374 try 375 { 376 while ( subentries.next() ) 377 { 378 Entry candidate = subentries.get(); 379 Dn candidateDn = candidate.getDn(); 380 381 if ( directoryService.getEvaluator().evaluate( ss, apDn, candidateDn, candidate ) ) 382 { 383 List<Modification> modifications = null; 384 385 switch ( operation ) 386 { 387 case ADD: 388 modifications = getOperationalModsForAdd( candidate, operationalAttributes ); 389 break; 390 391 case REMOVE: 392 modifications = getOperationalModsForRemove( opContext.getDn(), candidate ); 393 break; 394 395 case REPLACE: 396 // TODO: why is that commented out??? 397 //modifications = getOperationalModsForReplace( subentryDn, candidate ); 398 break; 399 400 default: 401 throw new IllegalArgumentException( "Unexpected operation " + operation ); 402 } 403 404 LOG.debug( "The entry {} has been evaluated to true for subentry {}", candidate.getDn(), opContext.getDn() ); 405 ModifyOperationContext modifyContext = new ModifyOperationContext( opContext.getSession(), candidateDn, modifications ); 406 modifyContext.setPartition( opContext.getPartition() ); 407 modifyContext.setTransaction( opContext.getTransaction() ); 408 409 nexus.modify( modifyContext ); 410 } 411 } 412 413 subentries.close(); 414 } 415 catch ( Exception e ) 416 { 417 throw new LdapOtherException( e.getMessage(), e ); 418 } 419 finally 420 { 421 try 422 { 423 subentries.close(); 424 } 425 catch ( Exception e ) 426 { 427 LOG.error( I18n.err( I18n.ERR_168 ), e ); 428 } 429 } 430 } 431 432 433 /** 434 * Checks if the given Dn is a namingContext 435 */ 436 private boolean isNamingContext( Dn dn ) throws LdapException 437 { 438 Dn namingContext = nexus.getSuffixDn( dn ); 439 440 return dn.equals( namingContext ); 441 } 442 443 444 /** 445 * Get the administrativePoint role 446 */ 447 private void checkAdministrativeRole( OperationContext opContext, Dn apDn ) throws LdapException 448 { 449 CoreSession session = opContext.getSession(); 450 LookupOperationContext lookupContext = new LookupOperationContext( session, apDn, 451 SchemaConstants.ALL_ATTRIBUTES_ARRAY ); 452 lookupContext.setPartition( opContext.getPartition() ); 453 lookupContext.setTransaction( opContext.getTransaction() ); 454 455 Entry administrationPoint = directoryService.getPartitionNexus().lookup( lookupContext ); 456 457 // The administrativeRole AT must exist and not be null 458 Attribute administrativeRole = administrationPoint.get( directoryService.getAtProvider() 459 .getAdministrativeRole() ); 460 461 // check that administrativeRole has something valid in it for us 462 if ( ( administrativeRole == null ) || ( administrativeRole.size() <= 0 ) ) 463 { 464 LOG.error( "The entry on {} is not an AdministrativePoint", apDn ); 465 throw new LdapNoSuchAttributeException( I18n.err( I18n.ERR_306, apDn ) ); 466 } 467 } 468 469 470 /** 471 * Get the SubtreeSpecification, parse it and stores it into the subentry 472 */ 473 private void setSubtreeSpecification( Subentry subentry, Entry entry ) throws LdapException 474 { 475 String subtree = entry.get( directoryService.getAtProvider().getSubtreeSpecification() ).getString(); 476 SubtreeSpecification ss; 477 478 try 479 { 480 ss = ssParser.parse( subtree ); 481 } 482 catch ( Exception e ) 483 { 484 String msg = I18n.err( I18n.ERR_307, entry.getDn() ); 485 LOG.warn( msg ); 486 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg ); 487 } 488 489 subentry.setSubtreeSpecification( ss ); 490 } 491 492 493 /** 494 * Checks to see if an entry being renamed has a descendant that is an 495 * administrative point. 496 * 497 * @param name the name of the entry which is used as the search base 498 * @return true if name is an administrative point or one of its descendants 499 * are, false otherwise 500 * @throws Exception if there are errors while searching the directory 501 */ 502 private boolean hasAdministrativeDescendant( OperationContext opContext, Dn name ) throws LdapException 503 { 504 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getAdministrativeRole() ); 505 SearchControls controls = new SearchControls(); 506 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 507 508 SearchOperationContext searchOperationContext = new SearchOperationContext( opContext.getSession(), name, 509 filter, controls ); 510 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 511 searchOperationContext.setTransaction( opContext.getTransaction() ); 512 searchOperationContext.setPartition( opContext.getPartition() ); 513 514 EntryFilteringCursor aps = nexus.search( searchOperationContext ); 515 516 try 517 { 518 if ( aps.next() ) 519 { 520 return true; 521 } 522 } 523 catch ( Exception e ) 524 { 525 throw new LdapOperationException( e.getMessage(), e ); 526 } 527 finally 528 { 529 try 530 { 531 aps.close(); 532 } 533 catch ( Exception e ) 534 { 535 LOG.error( I18n.err( I18n.ERR_168 ), e ); 536 } 537 } 538 539 return false; 540 } 541 542 543 private List<Modification> getModsOnEntryRdnChange( Dn oldName, Dn newName, Entry entry ) throws LdapException 544 { 545 List<Modification> modifications = new ArrayList<>(); 546 547 /* 548 * There are two different situations warranting action. First if 549 * an ss evalutating to true with the old name no longer evalutates 550 * to true with the new name. This would be caused by specific chop 551 * exclusions that effect the new name but did not effect the old 552 * name. In this case we must remove subentry operational attribute 553 * values associated with the dn of that subentry. 554 * 555 * In the second case an ss selects the entry with the new name when 556 * it did not previously with the old name. Again this situation 557 * would be caused by chop exclusions. In this case we must add subentry 558 * operational attribute values with the dn of this subentry. 559 */ 560 SubentryCache subentryCache = directoryService.getSubentryCache(); 561 SubtreeEvaluator evaluator = directoryService.getEvaluator(); 562 563 for ( Dn subentryDn : subentryCache ) 564 { 565 Dn apDn = subentryDn.getParent(); 566 SubtreeSpecification ss = subentryCache.getSubentry( subentryDn ).getSubtreeSpecification(); 567 boolean isOldNameSelected = evaluator.evaluate( ss, apDn, oldName, entry ); 568 boolean isNewNameSelected = evaluator.evaluate( ss, apDn, newName, entry ); 569 570 if ( isOldNameSelected == isNewNameSelected ) 571 { 572 continue; 573 } 574 575 // need to remove references to the subentry 576 if ( isOldNameSelected && !isNewNameSelected ) 577 { 578 for ( AttributeType operationalAttribute : directoryService.getAtProvider() 579 .getSubentryOperationalAttributes() ) 580 { 581 ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE; 582 Attribute opAttr = entry.get( operationalAttribute ); 583 584 if ( opAttr != null ) 585 { 586 opAttr = opAttr.clone(); 587 opAttr.remove( subentryDn.getName() ); 588 589 if ( opAttr.size() < 1 ) 590 { 591 op = ModificationOperation.REMOVE_ATTRIBUTE; 592 } 593 594 modifications.add( new DefaultModification( op, opAttr ) ); 595 } 596 } 597 } 598 // need to add references to the subentry 599 else if ( isNewNameSelected && !isOldNameSelected ) 600 { 601 for ( AttributeType operationalAttribute : directoryService.getAtProvider() 602 .getSubentryOperationalAttributes() ) 603 { 604 ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE; 605 Attribute opAttr = new DefaultAttribute( operationalAttribute ); 606 opAttr.add( subentryDn.getName() ); 607 modifications.add( new DefaultModification( op, opAttr ) ); 608 } 609 } 610 } 611 612 return modifications; 613 } 614 615 616 // ----------------------------------------------------------------------- 617 // Methods dealing with subentry modification 618 // ----------------------------------------------------------------------- 619 620 private Set<AdministrativeRole> getSubentryTypes( Entry entry, List<Modification> mods ) throws LdapException 621 { 622 Attribute ocFinalState = entry.get( directoryService.getAtProvider().getObjectClass() ).clone(); 623 624 for ( Modification mod : mods ) 625 { 626 if ( mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT ) 627 || mod.getAttribute().getId().equalsIgnoreCase( SchemaConstants.OBJECT_CLASS_AT_OID ) ) 628 { 629 switch ( mod.getOperation() ) 630 { 631 case ADD_ATTRIBUTE: 632 for ( Value value : mod.getAttribute() ) 633 { 634 ocFinalState.add( value.getString() ); 635 } 636 637 break; 638 639 case REMOVE_ATTRIBUTE: 640 for ( Value value : mod.getAttribute() ) 641 { 642 ocFinalState.remove( value.getString() ); 643 } 644 645 break; 646 647 case REPLACE_ATTRIBUTE: 648 ocFinalState = mod.getAttribute(); 649 break; 650 651 default: 652 throw new IllegalArgumentException( "Unexpected modify operatoin " + mod.getOperation() ); 653 } 654 } 655 } 656 657 Entry attrs = new DefaultEntry( schemaManager, Dn.ROOT_DSE ); 658 attrs.put( ocFinalState ); 659 return getSubentryAdminRoles( attrs ); 660 } 661 662 663 /** 664 * Update the list of modifications with a modification associated with a specific 665 * role, if it's requested. 666 */ 667 private void getOperationalModForReplace( boolean hasRole, AttributeType attributeType, Entry entry, Dn oldDn, 668 Dn newDn, List<Modification> modifications ) throws LdapInvalidAttributeValueException 669 { 670 String oldDnStr = oldDn.getName(); 671 String newDnStr = newDn.getName(); 672 673 if ( hasRole ) 674 { 675 Attribute operational = entry.get( attributeType ).clone(); 676 677 if ( operational == null ) 678 { 679 operational = new DefaultAttribute( attributeType, newDnStr ); 680 } 681 else 682 { 683 operational.remove( oldDnStr ); 684 operational.add( newDnStr ); 685 } 686 687 modifications.add( new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, operational ) ); 688 } 689 } 690 691 692 /** 693 * Get the list of modifications to be applied on an entry to inject the operational attributes 694 * associated with the administrative roles. 695 */ 696 private List<Modification> getOperationalModsForReplace( Dn oldDn, Dn newDn, Subentry subentry, Entry entry ) 697 throws LdapException 698 { 699 List<Modification> modifications = new ArrayList<>(); 700 701 getOperationalModForReplace( subentry.isAccessControlAdminRole(), directoryService.getAtProvider() 702 .getAccessControlSubentries(), entry, oldDn, newDn, modifications ); 703 getOperationalModForReplace( subentry.isSchemaAdminRole(), directoryService.getAtProvider() 704 .getSubschemaSubentry(), entry, oldDn, newDn, modifications ); 705 getOperationalModForReplace( subentry.isCollectiveAdminRole(), directoryService.getAtProvider() 706 .getCollectiveAttributeSubentries(), entry, oldDn, newDn, modifications ); 707 getOperationalModForReplace( subentry.isTriggersAdminRole(), directoryService.getAtProvider() 708 .getTriggerExecutionSubentries(), entry, oldDn, newDn, modifications ); 709 710 return modifications; 711 } 712 713 714 /** 715 * Gets the subschema operational attributes to be added to or removed from 716 * an entry selected by a subentry's subtreeSpecification. 717 */ 718 private List<Attribute> getSubentryOperationalAttributes( Dn dn, Subentry subentry ) throws LdapException 719 { 720 List<Attribute> attributes = new ArrayList<>(); 721 722 if ( subentry.isAccessControlAdminRole() ) 723 { 724 Attribute accessControlSubentries = new DefaultAttribute( directoryService.getAtProvider() 725 .getAccessControlSubentries(), dn.getName() ); 726 attributes.add( accessControlSubentries ); 727 } 728 729 if ( subentry.isSchemaAdminRole() ) 730 { 731 Attribute subschemaSubentry = new DefaultAttribute( 732 directoryService.getAtProvider().getSubschemaSubentry(), dn.getName() ); 733 attributes.add( subschemaSubentry ); 734 } 735 736 if ( subentry.isCollectiveAdminRole() ) 737 { 738 Attribute collectiveAttributeSubentries = new DefaultAttribute( directoryService.getAtProvider() 739 .getCollectiveAttributeSubentries(), dn.getName() ); 740 attributes.add( collectiveAttributeSubentries ); 741 } 742 743 if ( subentry.isTriggersAdminRole() ) 744 { 745 Attribute tiggerExecutionSubentries = new DefaultAttribute( directoryService.getAtProvider() 746 .getTriggerExecutionSubentries(), dn.getName() ); 747 attributes.add( tiggerExecutionSubentries ); 748 } 749 750 return attributes; 751 } 752 753 754 /** 755 * Calculates the subentry operational attributes to remove from a candidate 756 * entry selected by a subtreeSpecification. When we remove a subentry we 757 * must remove the operational attributes in the entries that were once selected 758 * by the subtree specification of that subentry. To do so we must perform 759 * a modify operation with the set of modifications to perform. This method 760 * calculates those modifications. 761 * 762 * @param subentryDn the distinguished name of the subentry 763 * @param candidate the candidate entry to removed from the 764 * @return the set of modifications required to remove an entry's reference to 765 * a subentry 766 */ 767 private List<Modification> getOperationalModsForRemove( Dn subentryDn, Entry candidate ) throws LdapException 768 { 769 List<Modification> modifications = new ArrayList<>(); 770 String dn = subentryDn.getName(); 771 772 for ( AttributeType operationalAttribute : directoryService.getAtProvider().getSubentryOperationalAttributes() ) 773 { 774 Attribute opAttr = candidate.get( operationalAttribute ); 775 776 if ( ( opAttr != null ) && opAttr.contains( dn ) ) 777 { 778 Attribute attr = new DefaultAttribute( operationalAttribute, dn ); 779 modifications.add( new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, attr ) ); 780 } 781 } 782 783 return modifications; 784 } 785 786 787 /** 788 * Calculates the subentry operational attributes to add or replace from 789 * a candidate entry selected by a subtree specification. When a subentry 790 * is added or it's specification is modified some entries must have new 791 * operational attributes added to it to point back to the associated 792 * subentry. To do so a modify operation must be performed on entries 793 * selected by the subtree specification. This method calculates the 794 * modify operation to be performed on the entry. 795 */ 796 private List<Modification> getOperationalModsForAdd( Entry entry, List<Attribute> operationalAttributes ) 797 throws LdapException 798 { 799 List<Modification> modifications = new ArrayList<>(); 800 801 for ( Attribute operationalAttribute : operationalAttributes ) 802 { 803 Attribute opAttrInEntry = entry.get( operationalAttribute.getAttributeType() ); 804 805 if ( ( opAttrInEntry != null ) && ( opAttrInEntry.size() > 0 ) ) 806 { 807 Attribute newOperationalAttribute = operationalAttribute.clone(); 808 809 for ( Value value : opAttrInEntry ) 810 { 811 newOperationalAttribute.add( value ); 812 } 813 814 modifications.add( new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 815 newOperationalAttribute ) ); 816 } 817 else 818 { 819 modifications 820 .add( new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, operationalAttribute ) ); 821 } 822 } 823 824 return modifications; 825 } 826 827 828 /** 829 * Get the list of modification to apply to all the entries 830 */ 831 private List<Modification> getModsOnEntryModification( Dn name, Entry oldEntry, Entry newEntry ) 832 throws LdapException 833 { 834 List<Modification> modList = new ArrayList<>(); 835 836 for ( Dn subentryDn : directoryService.getSubentryCache() ) 837 { 838 Dn apDn = subentryDn.getParent(); 839 SubtreeSpecification ss = directoryService.getSubentryCache().getSubentry( subentryDn ) 840 .getSubtreeSpecification(); 841 boolean isOldEntrySelected = directoryService.getEvaluator().evaluate( ss, apDn, name, oldEntry ); 842 boolean isNewEntrySelected = directoryService.getEvaluator().evaluate( ss, apDn, name, newEntry ); 843 844 if ( isOldEntrySelected == isNewEntrySelected ) 845 { 846 continue; 847 } 848 849 // need to remove references to the subentry 850 if ( isOldEntrySelected && !isNewEntrySelected ) 851 { 852 for ( AttributeType operationalAttribute : directoryService.getAtProvider() 853 .getSubentryOperationalAttributes() ) 854 { 855 ModificationOperation op = ModificationOperation.REPLACE_ATTRIBUTE; 856 Attribute opAttr = oldEntry.get( operationalAttribute ); 857 858 if ( opAttr != null ) 859 { 860 opAttr = opAttr.clone(); 861 opAttr.remove( subentryDn.getName() ); 862 863 if ( opAttr.size() < 1 ) 864 { 865 op = ModificationOperation.REMOVE_ATTRIBUTE; 866 } 867 868 modList.add( new DefaultModification( op, opAttr ) ); 869 } 870 } 871 } 872 // need to add references to the subentry 873 else if ( isNewEntrySelected && !isOldEntrySelected ) 874 { 875 for ( AttributeType operationalAttribute : directoryService.getAtProvider() 876 .getSubentryOperationalAttributes() ) 877 { 878 ModificationOperation op = ModificationOperation.ADD_ATTRIBUTE; 879 Attribute opAttr = new DefaultAttribute( operationalAttribute ); 880 opAttr.add( subentryDn.getName() ); 881 modList.add( new DefaultModification( op, opAttr ) ); 882 } 883 } 884 } 885 886 return modList; 887 } 888 889 890 /** 891 * Update the Operational Attribute with the reference to the subentry 892 */ 893 private void setOperationalAttribute( Entry entry, Dn subentryDn, AttributeType opAttr ) throws LdapException 894 { 895 Attribute operational = entry.get( opAttr ); 896 897 if ( operational == null ) 898 { 899 operational = new DefaultAttribute( opAttr ); 900 entry.put( operational ); 901 } 902 903 operational.add( subentryDn.getName() ); 904 } 905 906 907 //------------------------------------------------------------------------------------------- 908 // Interceptor API methods 909 //------------------------------------------------------------------------------------------- 910 /** 911 * {@inheritDoc} 912 */ 913 @Override 914 public void add( AddOperationContext addContext ) throws LdapException 915 { 916 Dn dn = addContext.getDn(); 917 Entry entry = addContext.getEntry(); 918 919 // Check if the added entry is a subentry 920 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 921 { 922 // get the name of the administrative point and its administrativeRole attributes 923 // The AP must be the parent Dn, but we also have to check that the given Dn 924 // is not the rootDSE or a NamingContext 925 if ( dn.isRootDse() || isNamingContext( dn ) ) 926 { 927 // Not allowed : we can't get a parent in those cases 928 throw new LdapOtherException( "Cannot find an AdministrativePoint for " + dn ); 929 } 930 931 // Get the administrativePoint role : we must have one immediately 932 // upper 933 Dn apDn = dn.getParent(); 934 checkAdministrativeRole( addContext, apDn ); 935 936 /* ---------------------------------------------------------------- 937 * Build the set of operational attributes to be injected into 938 * entries that are contained within the subtree represented by this 939 * new subentry. In the process we make sure the proper roles are 940 * supported by the administrative point to allow the addition of 941 * this new subentry. 942 * ---------------------------------------------------------------- 943 */ 944 Subentry subentry = new Subentry(); 945 subentry.setAdministrativeRoles( getSubentryAdminRoles( entry ) ); 946 List<Attribute> operationalAttributes = getSubentryOperationalAttributes( dn, subentry ); 947 948 /* ---------------------------------------------------------------- 949 * Parse the subtreeSpecification of the subentry and add it to the 950 * SubtreeSpecification cache. If the parse succeeds we continue 951 * to add the entry to the DIT. Thereafter we search out entries 952 * to modify the subentry operational attributes of. 953 * ---------------------------------------------------------------- 954 */ 955 setSubtreeSpecification( subentry, entry ); 956 directoryService.getSubentryCache().addSubentry( dn, subentry ); 957 958 // Now inject the subentry into the backend 959 next( addContext ); 960 961 /* ---------------------------------------------------------------- 962 * Find the baseDn for the subentry and use that to search the tree 963 * while testing each entry returned for inclusion within the 964 * subtree of the subentry's subtreeSpecification. All included 965 * entries will have their operational attributes merged with the 966 * operational attributes calculated above. 967 * ---------------------------------------------------------------- 968 */ 969 Dn baseDn = apDn; 970 baseDn = baseDn.add( subentry.getSubtreeSpecification().getBase() ); 971 972 updateEntries( addContext, OperationEnum.ADD, apDn, subentry.getSubtreeSpecification(), 973 baseDn, operationalAttributes ); 974 975 // Store the newly modified entry into the context for later use in interceptor 976 // just in case 977 addContext.setEntry( entry ); 978 } 979 else 980 { 981 // The added entry is not a Subentry. 982 // Nevertheless, we have to check if the entry is added into an AdministrativePoint 983 // and is associated with some SubtreeSpecification 984 // We brutally check *all* the subentries, as we don't hold a hierarchy 985 // of AP 986 // TODO : add a hierarchy of subentries 987 for ( Dn subentryDn : directoryService.getSubentryCache() ) 988 { 989 Dn apDn = subentryDn.getParent(); 990 991 // No need to evaluate the entry if it's not below an AP. 992 if ( dn.isDescendantOf( apDn ) ) 993 { 994 Subentry subentry = directoryService.getSubentryCache().getSubentry( subentryDn ); 995 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 996 997 // Now, evaluate the entry wrt the subentry ss 998 // and inject a ref to the subentry if it evaluates to true 999 if ( directoryService.getEvaluator().evaluate( ss, apDn, dn, entry ) ) 1000 { 1001 1002 if ( subentry.isAccessControlAdminRole() ) 1003 { 1004 setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider() 1005 .getAccessControlSubentries() ); 1006 } 1007 1008 if ( subentry.isSchemaAdminRole() ) 1009 { 1010 setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider() 1011 .getSubschemaSubentry() ); 1012 } 1013 1014 if ( subentry.isCollectiveAdminRole() ) 1015 { 1016 setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider() 1017 .getCollectiveAttributeSubentries() ); 1018 } 1019 1020 if ( subentry.isTriggersAdminRole() ) 1021 { 1022 setOperationalAttribute( entry, subentryDn, directoryService.getAtProvider() 1023 .getTriggerExecutionSubentries() ); 1024 } 1025 } 1026 } 1027 } 1028 1029 // Now that the entry has been updated with the operational attributes, 1030 // we can update it into the add context 1031 addContext.setEntry( entry ); 1032 1033 // Propagate the addition down to the backend. 1034 next( addContext ); 1035 } 1036 } 1037 1038 1039 /** 1040 * {@inheritDoc} 1041 */ 1042 @Override 1043 public void delete( DeleteOperationContext deleteContext ) throws LdapException 1044 { 1045 Dn dn = deleteContext.getDn(); 1046 Entry entry = deleteContext.getEntry(); 1047 1048 // If the entry has a "subentry" Objectclass, we can process the entry. 1049 // We first remove the re 1050 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1051 { 1052 Subentry removedSubentry = directoryService.getSubentryCache().getSubentry( dn ); 1053 1054 /* ---------------------------------------------------------------- 1055 * Find the baseDn for the subentry and use that to search the tree 1056 * for all entries included by the subtreeSpecification. Then we 1057 * check the entry for subentry operational attribute that contain 1058 * the Dn of the subentry. These are the subentry operational 1059 * attributes we remove from the entry in a modify operation. 1060 * ---------------------------------------------------------------- 1061 */ 1062 Dn apDn = dn.getParent(); 1063 Dn baseDn = apDn; 1064 baseDn = baseDn.add( removedSubentry.getSubtreeSpecification().getBase() ); 1065 1066 // Remove all the references to this removed subentry from all the selected entries 1067 updateEntries( deleteContext, OperationEnum.REMOVE, apDn, 1068 removedSubentry.getSubtreeSpecification(), baseDn, null ); 1069 1070 // Update the cache 1071 directoryService.getSubentryCache().removeSubentry( dn ); 1072 1073 // Now delete the subentry itself 1074 next( deleteContext ); 1075 } 1076 else 1077 { 1078 // TODO : deal with AP removal. 1079 next( deleteContext ); 1080 } 1081 } 1082 1083 1084 /** 1085 * {@inheritDoc} 1086 */ 1087 @Override 1088 public void modify( ModifyOperationContext modifyContext ) throws LdapException 1089 { 1090 Dn dn = modifyContext.getDn(); 1091 List<Modification> modifications = modifyContext.getModItems(); 1092 1093 Entry entry = modifyContext.getEntry(); 1094 1095 // We have three types of modifications : 1096 // 1) A modification applied on a normal entry 1097 // 2) A modification done on a subentry (the entry will have a 'subentry' ObjectClass) 1098 // 3) A modification on a normal entry on whch we add a 'subentry' ObjectClass 1099 // The third case is a transformation of a normal entry to a subentry. Not sure if it's 1100 // legal ... 1101 1102 boolean isSubtreeSpecificationModification = false; 1103 Modification subtreeMod = null; 1104 1105 // Find the subtreeSpecification 1106 for ( Modification mod : modifications ) 1107 { 1108 if ( mod.getAttribute().getAttributeType() 1109 .equals( directoryService.getAtProvider().getSubtreeSpecification() ) ) 1110 { 1111 isSubtreeSpecificationModification = true; 1112 subtreeMod = mod; 1113 break; 1114 } 1115 } 1116 1117 boolean containsSubentryOC = entry.contains( directoryService.getAtProvider().getObjectClass(), 1118 SchemaConstants.SUBENTRY_OC ); 1119 1120 // Check if we have a modified subentry attribute in a Subentry entry 1121 if ( containsSubentryOC && isSubtreeSpecificationModification ) 1122 { 1123 Subentry subentry = directoryService.getSubentryCache().removeSubentry( dn ); 1124 SubtreeSpecification ssOld = subentry.getSubtreeSpecification(); 1125 SubtreeSpecification ssNew; 1126 1127 try 1128 { 1129 ssNew = ssParser.parse( subtreeMod.getAttribute().getString() ); 1130 } 1131 catch ( Exception e ) 1132 { 1133 String msg = I18n.err( I18n.ERR_71 ); 1134 LOG.error( msg, e ); 1135 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, msg ); 1136 } 1137 1138 subentry.setSubtreeSpecification( ssNew ); 1139 subentry.setAdministrativeRoles( getSubentryTypes( entry, modifications ) ); 1140 directoryService.getSubentryCache().addSubentry( dn, subentry ); 1141 1142 next( modifyContext ); 1143 1144 // search for all entries selected by the old SS and remove references to subentry 1145 Dn apName = dn.getParent(); 1146 Dn oldBaseDn = apName; 1147 oldBaseDn = oldBaseDn.add( ssOld.getBase() ); 1148 1149 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1150 SearchControls controls = new SearchControls(); 1151 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1152 controls.setReturningAttributes( new String[] 1153 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1154 1155 SearchOperationContext searchOperationContext = new SearchOperationContext( modifyContext.getSession(), 1156 oldBaseDn, filter, controls ); 1157 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1158 searchOperationContext.setPartition( modifyContext.getPartition() ); 1159 searchOperationContext.setTransaction( modifyContext.getTransaction() ); 1160 1161 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1162 1163 try 1164 { 1165 while ( subentries.next() ) 1166 { 1167 Entry candidate = subentries.get(); 1168 Dn candidateDn = candidate.getDn(); 1169 1170 if ( directoryService.getEvaluator().evaluate( ssOld, apName, candidateDn, candidate ) ) 1171 { 1172 ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), candidateDn, 1173 getOperationalModsForRemove( dn, candidate ) ); 1174 newModifyContext.setPartition( modifyContext.getPartition() ); 1175 newModifyContext.setTransaction( modifyContext.getTransaction() ); 1176 1177 nexus.modify( newModifyContext ); 1178 } 1179 } 1180 1181 subentries.close(); 1182 } 1183 catch ( Exception e ) 1184 { 1185 throw new LdapOperationErrorException( e.getMessage(), e ); 1186 } 1187 finally 1188 { 1189 try 1190 { 1191 subentries.close(); 1192 } 1193 catch ( Exception e ) 1194 { 1195 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1196 } 1197 } 1198 1199 // search for all selected entries by the new SS and add references to subentry 1200 subentry = directoryService.getSubentryCache().getSubentry( dn ); 1201 List<Attribute> operationalAttributes = getSubentryOperationalAttributes( dn, subentry ); 1202 Dn newBaseDn = apName; 1203 newBaseDn = newBaseDn.add( ssNew.getBase() ); 1204 1205 searchOperationContext = new SearchOperationContext( modifyContext.getSession(), newBaseDn, filter, 1206 controls ); 1207 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1208 searchOperationContext.setPartition( modifyContext.getPartition() ); 1209 searchOperationContext.setTransaction( modifyContext.getTransaction() ); 1210 1211 subentries = nexus.search( searchOperationContext ); 1212 1213 try 1214 { 1215 while ( subentries.next() ) 1216 { 1217 Entry candidate = subentries.get(); 1218 Dn candidateDn = candidate.getDn(); 1219 1220 if ( directoryService.getEvaluator().evaluate( ssNew, apName, candidateDn, candidate ) ) 1221 { 1222 ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), candidateDn, 1223 getOperationalModsForAdd( candidate, operationalAttributes ) ); 1224 newModifyContext.setTransaction( modifyContext.getTransaction() ); 1225 nexus.modify( newModifyContext ); 1226 } 1227 } 1228 subentries.close(); 1229 } 1230 catch ( Exception e ) 1231 { 1232 throw new LdapOperationErrorException( e.getMessage(), e ); 1233 } 1234 finally 1235 { 1236 try 1237 { 1238 subentries.close(); 1239 } 1240 catch ( Exception e ) 1241 { 1242 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1243 } 1244 } 1245 } 1246 else 1247 { 1248 next( modifyContext ); 1249 1250 if ( !containsSubentryOC ) 1251 { 1252 Entry newEntry = modifyContext.getAlteredEntry(); 1253 1254 List<Modification> subentriesOpAttrMods = getModsOnEntryModification( dn, entry, newEntry ); 1255 1256 if ( !subentriesOpAttrMods.isEmpty() ) 1257 { 1258 ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), dn, subentriesOpAttrMods ); 1259 newModifyContext.setPartition( modifyContext.getPartition() ); 1260 newModifyContext.setTransaction( modifyContext.getTransaction() ); 1261 nexus.modify( newModifyContext ); 1262 } 1263 } 1264 } 1265 } 1266 1267 1268 /** 1269 * The Move operation for a Subentry will deal with different cases : 1270 * 1) we move a normal entry 1271 * 2) we move a subentry 1272 * 3) we move an administrationPoint 1273 * <p> 1274 * <u>Case 1 :</u><br> 1275 * A normal entry (ie, not a subentry or an AP) may be part of some administrative areas. 1276 * We have to remove the references to the associated areas if the entry gets out of them.<br> 1277 * This entry can also be moved to some other administrative area, and it should then be 1278 * updated to point to the associated subentries. 1279 * <br><br> 1280 * There is one preliminary condition : If the entry has a descendant which is an 1281 * Administrative Point, then the move cannot be done. 1282 * <br><br> 1283 * <u>Case 2 :</u><br> 1284 * The subentry has to be moved under a new AP, otherwise this is an error. Once moved, 1285 * we have to update all the entries selected by the old subtreeSpecification, removing 1286 * the references to the subentry from all the selected entry, and update the entries 1287 * selected by the new subtreeSpecification by adding a reference to the subentry into them. 1288 * <br><br> 1289 * <u>Case 3 :</u><br> 1290 * 1291 * 1292 * @param moveContext The context containing all the needed informations to proceed 1293 * @throws LdapException If the move failed 1294 */ 1295 @Override 1296 public void move( MoveOperationContext moveContext ) throws LdapException 1297 { 1298 Dn oldDn = moveContext.getDn(); 1299 Dn newSuperiorDn = moveContext.getNewSuperior(); 1300 1301 Entry entry = moveContext.getOriginalEntry(); 1302 1303 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1304 { 1305 // This is a subentry. Moving a subentry means we have to: 1306 // o Check that there is a new AP where we move the subentry 1307 // o Remove the op Attr from all the entry selected by the subentry 1308 // o Add the op Attr in all the selected entry by the subentry 1309 1310 // If we move it, we have to check that 1311 // the new parent is an AP 1312 checkAdministrativeRole( moveContext, newSuperiorDn ); 1313 1314 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1315 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1316 Dn apName = oldDn.getParent(); 1317 Dn baseDn = apName; 1318 baseDn = baseDn.add( ss.getBase() ); 1319 Dn newName = newSuperiorDn; 1320 newName = newName.add( oldDn.getRdn() ); 1321 1322 if ( !newName.isSchemaAware() ) 1323 { 1324 newName = new Dn( schemaManager, newName ); 1325 } 1326 1327 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1328 1329 next( moveContext ); 1330 1331 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1332 1333 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1334 SearchControls controls = new SearchControls(); 1335 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1336 controls.setReturningAttributes( new String[] 1337 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1338 1339 SearchOperationContext searchOperationContext = new SearchOperationContext( moveContext.getSession(), 1340 baseDn, 1341 filter, controls ); 1342 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1343 searchOperationContext.setPartition( moveContext.getPartition() ); 1344 searchOperationContext.setTransaction( moveContext.getTransaction() ); 1345 1346 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1347 1348 try 1349 { 1350 // Modify all the entries under this subentry 1351 while ( subentries.next() ) 1352 { 1353 Entry candidate = subentries.get(); 1354 Dn dn = candidate.getDn(); 1355 1356 if ( !dn.isSchemaAware() ) 1357 { 1358 dn = new Dn( schemaManager, dn ); 1359 } 1360 1361 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1362 { 1363 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), dn, 1364 getOperationalModsForReplace( oldDn, newName, subentry, candidate ) ); 1365 newModifyContext.setPartition( moveContext.getPartition() ); 1366 newModifyContext.setTransaction( moveContext.getTransaction() ); 1367 nexus.modify( newModifyContext ); 1368 } 1369 } 1370 } 1371 catch ( Exception e ) 1372 { 1373 throw new LdapOperationException( e.getMessage(), e ); 1374 } 1375 finally 1376 { 1377 try 1378 { 1379 subentries.close(); 1380 } 1381 catch ( Exception e ) 1382 { 1383 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1384 } 1385 } 1386 } 1387 else 1388 { 1389 // A normal entry. It may be part of a SubtreeSpecifciation. In this 1390 // case, we have to update the opAttrs (removing old ones and adding the 1391 // new ones) 1392 1393 // First, an moved entry which has an AP in one of its descendant 1394 // can't be moved. 1395 if ( hasAdministrativeDescendant( moveContext, oldDn ) ) 1396 { 1397 String msg = I18n.err( I18n.ERR_308 ); 1398 LOG.warn( msg ); 1399 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1400 } 1401 1402 // Move the entry 1403 next( moveContext ); 1404 1405 // calculate the new Dn now for use below to modify subentry operational 1406 // attributes contained within this regular entry with name changes 1407 Dn newDn = moveContext.getNewDn(); 1408 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry ); 1409 1410 // Update the entry operational attributes 1411 if ( !mods.isEmpty() ) 1412 { 1413 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), newDn, mods ); 1414 newModifyContext.setPartition( moveContext.getPartition() ); 1415 newModifyContext.setTransaction( moveContext.getTransaction() ); 1416 nexus.modify( newModifyContext ); 1417 } 1418 } 1419 } 1420 1421 1422 /** 1423 * {@inheritDoc} 1424 */ 1425 @Override 1426 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 1427 { 1428 Dn oldDn = moveAndRenameContext.getDn(); 1429 Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn(); 1430 1431 Entry entry = moveAndRenameContext.getOriginalEntry(); 1432 1433 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1434 { 1435 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1436 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1437 Dn apName = oldDn.getParent(); 1438 Dn baseDn = apName; 1439 baseDn = baseDn.add( ss.getBase() ); 1440 Dn newName = newSuperiorDn.getParent(); 1441 1442 newName = newName.add( moveAndRenameContext.getNewRdn() ); 1443 1444 if ( !newName.isSchemaAware() ) 1445 { 1446 newName = new Dn( schemaManager, newName ); 1447 } 1448 1449 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1450 1451 next( moveAndRenameContext ); 1452 1453 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1454 1455 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1456 SearchControls controls = new SearchControls(); 1457 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1458 controls.setReturningAttributes( new String[] 1459 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1460 1461 SearchOperationContext searchOperationContext = new SearchOperationContext( 1462 moveAndRenameContext.getSession(), baseDn, 1463 filter, controls ); 1464 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1465 searchOperationContext.setPartition( moveAndRenameContext.getPartition() ); 1466 searchOperationContext.setTransaction( moveAndRenameContext.getTransaction() ); 1467 1468 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1469 1470 try 1471 { 1472 while ( subentries.next() ) 1473 { 1474 Entry candidate = subentries.get(); 1475 Dn dn = candidate.getDn(); 1476 1477 if ( !dn.isSchemaAware() ) 1478 { 1479 dn = new Dn( schemaManager, dn ); 1480 } 1481 1482 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1483 { 1484 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveAndRenameContext.getSession(), dn, 1485 getOperationalModsForReplace( oldDn, newName, subentry, candidate ) ); 1486 newModifyContext.setPartition( moveAndRenameContext.getPartition() ); 1487 newModifyContext.setTransaction( moveAndRenameContext.getTransaction() ); 1488 nexus.modify( newModifyContext ); 1489 } 1490 } 1491 } 1492 catch ( Exception e ) 1493 { 1494 throw new LdapOperationException( e.getMessage(), e ); 1495 } 1496 finally 1497 { 1498 try 1499 { 1500 subentries.close(); 1501 } 1502 catch ( Exception e ) 1503 { 1504 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1505 } 1506 } 1507 } 1508 else 1509 { 1510 if ( hasAdministrativeDescendant( moveAndRenameContext, oldDn ) ) 1511 { 1512 String msg = I18n.err( I18n.ERR_308 ); 1513 LOG.warn( msg ); 1514 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1515 } 1516 1517 next( moveAndRenameContext ); 1518 1519 // calculate the new Dn now for use below to modify subentry operational 1520 // attributes contained within this regular entry with name changes 1521 Dn newDn = moveAndRenameContext.getNewDn(); 1522 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry ); 1523 1524 if ( !mods.isEmpty() ) 1525 { 1526 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveAndRenameContext.getSession(), newDn, mods ); 1527 newModifyContext.setTransaction( moveAndRenameContext.getTransaction() ); 1528 nexus.modify( newModifyContext ); 1529 } 1530 } 1531 } 1532 1533 1534 /** 1535 * {@inheritDoc} 1536 */ 1537 @Override 1538 public void rename( RenameOperationContext renameContext ) throws LdapException 1539 { 1540 Dn oldDn = renameContext.getDn(); 1541 1542 Entry entry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getClonedEntry(); 1543 1544 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1545 { 1546 // @Todo To be reviewed !!! 1547 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1548 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1549 Dn apName = oldDn.getParent(); 1550 Dn baseDn = apName; 1551 baseDn = baseDn.add( ss.getBase() ); 1552 Dn newName = oldDn.getParent(); 1553 1554 newName = newName.add( renameContext.getNewRdn() ); 1555 1556 if ( !newName.isSchemaAware() ) 1557 { 1558 newName = new Dn( schemaManager, newName ); 1559 } 1560 1561 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1562 next( renameContext ); 1563 1564 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1565 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1566 SearchControls controls = new SearchControls(); 1567 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1568 controls.setReturningAttributes( new String[] 1569 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1570 1571 SearchOperationContext searchOperationContext = new SearchOperationContext( renameContext.getSession(), 1572 baseDn, 1573 filter, controls ); 1574 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1575 searchOperationContext.setPartition( renameContext.getPartition() ); 1576 searchOperationContext.setTransaction( renameContext.getTransaction() ); 1577 1578 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1579 1580 try 1581 { 1582 while ( subentries.next() ) 1583 { 1584 Entry candidate = subentries.get(); 1585 Dn dn = candidate.getDn(); 1586 1587 if ( !dn.isSchemaAware() ) 1588 { 1589 dn = new Dn( schemaManager, dn ); 1590 } 1591 1592 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1593 { 1594 ModifyOperationContext newModifyContext = new ModifyOperationContext( renameContext.getSession(), dn, 1595 getOperationalModsForReplace( 1596 oldDn, newName, subentry, candidate ) ); 1597 newModifyContext.setTransaction( renameContext.getTransaction() ); 1598 nexus.modify( newModifyContext ); 1599 } 1600 } 1601 } 1602 catch ( Exception e ) 1603 { 1604 throw new LdapOperationException( e.getMessage(), e ); 1605 } 1606 finally 1607 { 1608 try 1609 { 1610 subentries.close(); 1611 } 1612 catch ( Exception e ) 1613 { 1614 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1615 } 1616 } 1617 } 1618 else 1619 { 1620 if ( hasAdministrativeDescendant( renameContext, oldDn ) ) 1621 { 1622 String msg = I18n.err( I18n.ERR_308 ); 1623 LOG.warn( msg ); 1624 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1625 } 1626 1627 next( renameContext ); 1628 1629 // calculate the new Dn now for use below to modify subentry operational 1630 // attributes contained within this regular entry with name changes 1631 Dn newName = renameContext.getNewDn(); 1632 1633 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newName, entry ); 1634 1635 if ( !mods.isEmpty() ) 1636 { 1637 ModifyOperationContext newModifyContext = new ModifyOperationContext( renameContext.getSession(), newName, mods ); 1638 newModifyContext.setPartition( renameContext.getPartition() ); 1639 newModifyContext.setTransaction( renameContext.getTransaction() ); 1640 nexus.modify( newModifyContext ); 1641 } 1642 } 1643 } 1644 1645 1646 /** 1647 * {@inheritDoc} 1648 */ 1649 @Override 1650 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 1651 { 1652 EntryFilteringCursor cursor = next( searchContext ); 1653 1654 // object scope searches by default return subentries 1655 if ( searchContext.getScope() == SearchScope.OBJECT ) 1656 { 1657 return cursor; 1658 } 1659 1660 // DO NOT hide subentries for replication operations 1661 if ( searchContext.isSyncreplSearch() ) 1662 { 1663 return cursor; 1664 } 1665 1666 // for subtree and one level scope we filter 1667 if ( !isSubentryVisible( searchContext ) ) 1668 { 1669 cursor.addEntryFilter( new HideSubentriesFilter() ); 1670 } 1671 else 1672 { 1673 cursor.addEntryFilter( new HideEntriesFilter() ); 1674 } 1675 1676 return cursor; 1677 } 1678}