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 nexus.modify( new ModifyOperationContext( modifyContext.getSession(), candidateDn, 1223 getOperationalModsForAdd( candidate, operationalAttributes ) ) ); 1224 } 1225 } 1226 subentries.close(); 1227 } 1228 catch ( Exception e ) 1229 { 1230 throw new LdapOperationErrorException( e.getMessage(), e ); 1231 } 1232 finally 1233 { 1234 try 1235 { 1236 subentries.close(); 1237 } 1238 catch ( Exception e ) 1239 { 1240 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1241 } 1242 } 1243 } 1244 else 1245 { 1246 next( modifyContext ); 1247 1248 if ( !containsSubentryOC ) 1249 { 1250 Entry newEntry = modifyContext.getAlteredEntry(); 1251 1252 List<Modification> subentriesOpAttrMods = getModsOnEntryModification( dn, entry, newEntry ); 1253 1254 if ( !subentriesOpAttrMods.isEmpty() ) 1255 { 1256 ModifyOperationContext newModifyContext = new ModifyOperationContext( modifyContext.getSession(), dn, subentriesOpAttrMods ); 1257 newModifyContext.setPartition( modifyContext.getPartition() ); 1258 newModifyContext.setTransaction( modifyContext.getTransaction() ); 1259 nexus.modify( newModifyContext ); 1260 } 1261 } 1262 } 1263 } 1264 1265 1266 /** 1267 * The Move operation for a Subentry will deal with different cases : 1268 * 1) we move a normal entry 1269 * 2) we move a subentry 1270 * 3) we move an administrationPoint 1271 * <p> 1272 * <u>Case 1 :</u><br> 1273 * A normal entry (ie, not a subentry or an AP) may be part of some administrative areas. 1274 * We have to remove the references to the associated areas if the entry gets out of them.<br> 1275 * This entry can also be moved to some other administrative area, and it should then be 1276 * updated to point to the associated subentries. 1277 * <br><br> 1278 * There is one preliminary condition : If the entry has a descendant which is an 1279 * Administrative Point, then the move cannot be done. 1280 * <br><br> 1281 * <u>Case 2 :</u><br> 1282 * The subentry has to be moved under a new AP, otherwise this is an error. Once moved, 1283 * we have to update all the entries selected by the old subtreeSpecification, removing 1284 * the references to the subentry from all the selected entry, and update the entries 1285 * selected by the new subtreeSpecification by adding a reference to the subentry into them. 1286 * <br><br> 1287 * <u>Case 3 :</u><br> 1288 * 1289 * 1290 * @param moveContext The context containing all the needed informations to proceed 1291 * @throws LdapException If the move failed 1292 */ 1293 @Override 1294 public void move( MoveOperationContext moveContext ) throws LdapException 1295 { 1296 Dn oldDn = moveContext.getDn(); 1297 Dn newSuperiorDn = moveContext.getNewSuperior(); 1298 1299 Entry entry = moveContext.getOriginalEntry(); 1300 1301 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1302 { 1303 // This is a subentry. Moving a subentry means we have to: 1304 // o Check that there is a new AP where we move the subentry 1305 // o Remove the op Attr from all the entry selected by the subentry 1306 // o Add the op Attr in all the selected entry by the subentry 1307 1308 // If we move it, we have to check that 1309 // the new parent is an AP 1310 checkAdministrativeRole( moveContext, newSuperiorDn ); 1311 1312 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1313 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1314 Dn apName = oldDn.getParent(); 1315 Dn baseDn = apName; 1316 baseDn = baseDn.add( ss.getBase() ); 1317 Dn newName = newSuperiorDn; 1318 newName = newName.add( oldDn.getRdn() ); 1319 1320 if ( !newName.isSchemaAware() ) 1321 { 1322 newName = new Dn( schemaManager, newName ); 1323 } 1324 1325 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1326 1327 next( moveContext ); 1328 1329 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1330 1331 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1332 SearchControls controls = new SearchControls(); 1333 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1334 controls.setReturningAttributes( new String[] 1335 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1336 1337 SearchOperationContext searchOperationContext = new SearchOperationContext( moveContext.getSession(), 1338 baseDn, 1339 filter, controls ); 1340 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1341 searchOperationContext.setPartition( moveContext.getPartition() ); 1342 searchOperationContext.setTransaction( moveContext.getTransaction() ); 1343 1344 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1345 1346 try 1347 { 1348 // Modify all the entries under this subentry 1349 while ( subentries.next() ) 1350 { 1351 Entry candidate = subentries.get(); 1352 Dn dn = candidate.getDn(); 1353 1354 if ( !dn.isSchemaAware() ) 1355 { 1356 dn = new Dn( schemaManager, dn ); 1357 } 1358 1359 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1360 { 1361 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), dn, 1362 getOperationalModsForReplace( oldDn, newName, subentry, candidate ) ); 1363 newModifyContext.setPartition( moveContext.getPartition() ); 1364 newModifyContext.setTransaction( moveContext.getTransaction() ); 1365 nexus.modify( newModifyContext ); 1366 } 1367 } 1368 } 1369 catch ( Exception e ) 1370 { 1371 throw new LdapOperationException( e.getMessage(), e ); 1372 } 1373 finally 1374 { 1375 try 1376 { 1377 subentries.close(); 1378 } 1379 catch ( Exception e ) 1380 { 1381 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1382 } 1383 } 1384 } 1385 else 1386 { 1387 // A normal entry. It may be part of a SubtreeSpecifciation. In this 1388 // case, we have to update the opAttrs (removing old ones and adding the 1389 // new ones) 1390 1391 // First, an moved entry which has an AP in one of its descendant 1392 // can't be moved. 1393 if ( hasAdministrativeDescendant( moveContext, oldDn ) ) 1394 { 1395 String msg = I18n.err( I18n.ERR_308 ); 1396 LOG.warn( msg ); 1397 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1398 } 1399 1400 // Move the entry 1401 next( moveContext ); 1402 1403 // calculate the new Dn now for use below to modify subentry operational 1404 // attributes contained within this regular entry with name changes 1405 Dn newDn = moveContext.getNewDn(); 1406 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry ); 1407 1408 // Update the entry operational attributes 1409 if ( !mods.isEmpty() ) 1410 { 1411 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveContext.getSession(), newDn, mods ); 1412 newModifyContext.setPartition( moveContext.getPartition() ); 1413 newModifyContext.setTransaction( moveContext.getTransaction() ); 1414 nexus.modify( newModifyContext ); 1415 } 1416 } 1417 } 1418 1419 1420 /** 1421 * {@inheritDoc} 1422 */ 1423 @Override 1424 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 1425 { 1426 Dn oldDn = moveAndRenameContext.getDn(); 1427 Dn newSuperiorDn = moveAndRenameContext.getNewSuperiorDn(); 1428 1429 Entry entry = moveAndRenameContext.getOriginalEntry(); 1430 1431 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1432 { 1433 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1434 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1435 Dn apName = oldDn.getParent(); 1436 Dn baseDn = apName; 1437 baseDn = baseDn.add( ss.getBase() ); 1438 Dn newName = newSuperiorDn.getParent(); 1439 1440 newName = newName.add( moveAndRenameContext.getNewRdn() ); 1441 1442 if ( !newName.isSchemaAware() ) 1443 { 1444 newName = new Dn( schemaManager, newName ); 1445 } 1446 1447 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1448 1449 next( moveAndRenameContext ); 1450 1451 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1452 1453 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1454 SearchControls controls = new SearchControls(); 1455 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1456 controls.setReturningAttributes( new String[] 1457 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1458 1459 SearchOperationContext searchOperationContext = new SearchOperationContext( 1460 moveAndRenameContext.getSession(), baseDn, 1461 filter, controls ); 1462 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1463 searchOperationContext.setPartition( moveAndRenameContext.getPartition() ); 1464 searchOperationContext.setTransaction( moveAndRenameContext.getTransaction() ); 1465 1466 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1467 1468 try 1469 { 1470 while ( subentries.next() ) 1471 { 1472 Entry candidate = subentries.get(); 1473 Dn dn = candidate.getDn(); 1474 1475 if ( !dn.isSchemaAware() ) 1476 { 1477 dn = new Dn( schemaManager, dn ); 1478 } 1479 1480 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1481 { 1482 ModifyOperationContext newModifyContext = new ModifyOperationContext( moveAndRenameContext.getSession(), dn, 1483 getOperationalModsForReplace( oldDn, newName, subentry, candidate ) ); 1484 newModifyContext.setPartition( moveAndRenameContext.getPartition() ); 1485 newModifyContext.setTransaction( moveAndRenameContext.getTransaction() ); 1486 nexus.modify( newModifyContext ); 1487 } 1488 } 1489 } 1490 catch ( Exception e ) 1491 { 1492 throw new LdapOperationException( e.getMessage(), e ); 1493 } 1494 finally 1495 { 1496 try 1497 { 1498 subentries.close(); 1499 } 1500 catch ( Exception e ) 1501 { 1502 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1503 } 1504 } 1505 } 1506 else 1507 { 1508 if ( hasAdministrativeDescendant( moveAndRenameContext, oldDn ) ) 1509 { 1510 String msg = I18n.err( I18n.ERR_308 ); 1511 LOG.warn( msg ); 1512 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1513 } 1514 1515 next( moveAndRenameContext ); 1516 1517 // calculate the new Dn now for use below to modify subentry operational 1518 // attributes contained within this regular entry with name changes 1519 Dn newDn = moveAndRenameContext.getNewDn(); 1520 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newDn, entry ); 1521 1522 if ( !mods.isEmpty() ) 1523 { 1524 nexus.modify( new ModifyOperationContext( moveAndRenameContext.getSession(), newDn, mods ) ); 1525 } 1526 } 1527 } 1528 1529 1530 /** 1531 * {@inheritDoc} 1532 */ 1533 @Override 1534 public void rename( RenameOperationContext renameContext ) throws LdapException 1535 { 1536 Dn oldDn = renameContext.getDn(); 1537 1538 Entry entry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getClonedEntry(); 1539 1540 if ( entry.contains( directoryService.getAtProvider().getObjectClass(), SchemaConstants.SUBENTRY_OC ) ) 1541 { 1542 // @Todo To be reviewed !!! 1543 Subentry subentry = directoryService.getSubentryCache().removeSubentry( oldDn ); 1544 SubtreeSpecification ss = subentry.getSubtreeSpecification(); 1545 Dn apName = oldDn.getParent(); 1546 Dn baseDn = apName; 1547 baseDn = baseDn.add( ss.getBase() ); 1548 Dn newName = oldDn.getParent(); 1549 1550 newName = newName.add( renameContext.getNewRdn() ); 1551 1552 if ( !newName.isSchemaAware() ) 1553 { 1554 newName = new Dn( schemaManager, newName ); 1555 } 1556 1557 directoryService.getSubentryCache().addSubentry( newName, subentry ); 1558 next( renameContext ); 1559 1560 subentry = directoryService.getSubentryCache().getSubentry( newName ); 1561 ExprNode filter = new PresenceNode( directoryService.getAtProvider().getObjectClass() ); 1562 SearchControls controls = new SearchControls(); 1563 controls.setSearchScope( SearchControls.SUBTREE_SCOPE ); 1564 controls.setReturningAttributes( new String[] 1565 { SchemaConstants.ALL_OPERATIONAL_ATTRIBUTES, SchemaConstants.ALL_USER_ATTRIBUTES } ); 1566 1567 SearchOperationContext searchOperationContext = new SearchOperationContext( renameContext.getSession(), 1568 baseDn, 1569 filter, controls ); 1570 searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES ); 1571 searchOperationContext.setPartition( renameContext.getPartition() ); 1572 searchOperationContext.setTransaction( renameContext.getTransaction() ); 1573 1574 EntryFilteringCursor subentries = nexus.search( searchOperationContext ); 1575 1576 try 1577 { 1578 while ( subentries.next() ) 1579 { 1580 Entry candidate = subentries.get(); 1581 Dn dn = candidate.getDn(); 1582 1583 if ( !dn.isSchemaAware() ) 1584 { 1585 dn = new Dn( schemaManager, dn ); 1586 } 1587 1588 if ( directoryService.getEvaluator().evaluate( ss, apName, dn, candidate ) ) 1589 { 1590 nexus.modify( new ModifyOperationContext( renameContext.getSession(), dn, 1591 getOperationalModsForReplace( 1592 oldDn, newName, subentry, candidate ) ) ); 1593 } 1594 } 1595 } 1596 catch ( Exception e ) 1597 { 1598 throw new LdapOperationException( e.getMessage(), e ); 1599 } 1600 finally 1601 { 1602 try 1603 { 1604 subentries.close(); 1605 } 1606 catch ( Exception e ) 1607 { 1608 LOG.error( I18n.err( I18n.ERR_168 ), e ); 1609 } 1610 } 1611 } 1612 else 1613 { 1614 if ( hasAdministrativeDescendant( renameContext, oldDn ) ) 1615 { 1616 String msg = I18n.err( I18n.ERR_308 ); 1617 LOG.warn( msg ); 1618 throw new LdapSchemaViolationException( ResultCodeEnum.NOT_ALLOWED_ON_RDN, msg ); 1619 } 1620 1621 next( renameContext ); 1622 1623 // calculate the new Dn now for use below to modify subentry operational 1624 // attributes contained within this regular entry with name changes 1625 Dn newName = renameContext.getNewDn(); 1626 1627 List<Modification> mods = getModsOnEntryRdnChange( oldDn, newName, entry ); 1628 1629 if ( !mods.isEmpty() ) 1630 { 1631 ModifyOperationContext newModifyContext = new ModifyOperationContext( renameContext.getSession(), newName, mods ); 1632 newModifyContext.setPartition( renameContext.getPartition() ); 1633 newModifyContext.setTransaction( renameContext.getTransaction() ); 1634 nexus.modify( newModifyContext ); 1635 } 1636 } 1637 } 1638 1639 1640 /** 1641 * {@inheritDoc} 1642 */ 1643 @Override 1644 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 1645 { 1646 EntryFilteringCursor cursor = next( searchContext ); 1647 1648 // object scope searches by default return subentries 1649 if ( searchContext.getScope() == SearchScope.OBJECT ) 1650 { 1651 return cursor; 1652 } 1653 1654 // DO NOT hide subentries for replication operations 1655 if ( searchContext.isSyncreplSearch() ) 1656 { 1657 return cursor; 1658 } 1659 1660 // for subtree and one level scope we filter 1661 if ( !isSubentryVisible( searchContext ) ) 1662 { 1663 cursor.addEntryFilter( new HideSubentriesFilter() ); 1664 } 1665 else 1666 { 1667 cursor.addEntryFilter( new HideEntriesFilter() ); 1668 } 1669 1670 return cursor; 1671 } 1672}