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.operational; 021 022 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.List; 027import java.util.Set; 028import java.util.UUID; 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.DefaultModification; 034import org.apache.directory.api.ldap.model.entry.Entry; 035import org.apache.directory.api.ldap.model.entry.Modification; 036import org.apache.directory.api.ldap.model.entry.ModificationOperation; 037import org.apache.directory.api.ldap.model.entry.Value; 038import org.apache.directory.api.ldap.model.exception.LdapException; 039import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException; 040import org.apache.directory.api.ldap.model.name.Ava; 041import org.apache.directory.api.ldap.model.name.Dn; 042import org.apache.directory.api.ldap.model.name.Rdn; 043import org.apache.directory.api.ldap.model.schema.AttributeType; 044import org.apache.directory.api.ldap.model.schema.AttributeTypeOptions; 045import org.apache.directory.api.ldap.model.schema.ObjectClass; 046import org.apache.directory.api.ldap.model.schema.SchemaManager; 047import org.apache.directory.api.util.DateUtils; 048import org.apache.directory.server.constants.ApacheSchemaConstants; 049import org.apache.directory.server.constants.ServerDNConstants; 050import org.apache.directory.server.core.api.DirectoryService; 051import org.apache.directory.server.core.api.InterceptorEnum; 052import org.apache.directory.server.core.api.entry.ClonedServerEntry; 053import org.apache.directory.server.core.api.filtering.EntryFilter; 054import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 055import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 056import org.apache.directory.server.core.api.interceptor.Interceptor; 057import org.apache.directory.server.core.api.interceptor.context.AddOperationContext; 058import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 059import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 060import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 061import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 062import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 063import org.apache.directory.server.core.api.interceptor.context.OperationContext; 064import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 065import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 066import org.apache.directory.server.core.api.partition.Partition; 067import org.apache.directory.server.core.api.partition.Subordinates; 068import org.apache.directory.server.core.shared.SchemaService; 069import org.apache.directory.server.i18n.I18n; 070import org.slf4j.Logger; 071import org.slf4j.LoggerFactory; 072 073 074/** 075 * An {@link Interceptor} that adds or modifies the default attributes 076 * of entries. There are six default attributes for now; 077 * <tt>'creatorsName'</tt>, <tt>'createTimestamp'</tt>, <tt>'modifiersName'</tt>, 078 * <tt>'modifyTimestamp'</tt>, <tt>entryUUID</tt> and <tt>entryCSN</tt>. 079 * 080 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 081 */ 082public class OperationalAttributeInterceptor extends BaseInterceptor 083{ 084 /** The LoggerFactory used by this Interceptor */ 085 private static final Logger LOG = LoggerFactory.getLogger( OperationalAttributeInterceptor.class ); 086 087 /** The denormalizer filter */ 088 private final EntryFilter denormalizingSearchFilter = new OperationalAttributeDenormalizingSearchFilter(); 089 090 /** The filter that add the mandatory operational attributes */ 091 private final EntryFilter operationalAttributeSearchFilter = new OperationalAttributeSearchFilter(); 092 093 /** The filter that add the subordinates operational attributes */ 094 private final EntryFilter subordinatesSearchFilter = new SubordinatesSearchFilter(); 095 096 /** The subschemasubentry Dn */ 097 private Dn subschemaSubentryDn; 098 099 /** The admin Dn */ 100 private Dn adminDn; 101 102 /** 103 * the search result filter to use for collective attribute injection 104 */ 105 private class OperationalAttributeDenormalizingSearchFilter implements EntryFilter 106 { 107 /** 108 * {@inheritDoc} 109 */ 110 @Override 111 public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException 112 { 113 if ( operation.getReturningAttributesString() == null ) 114 { 115 return true; 116 } 117 118 // Denormalize the operational Attributes 119 denormalizeEntryOpAttrs( entry ); 120 121 return true; 122 } 123 124 125 /** 126 * {@inheritDoc} 127 */ 128 @Override 129 public String toString( String tabs ) 130 { 131 return tabs + "OperationalAttributeDenormalizingSearchFilter"; 132 } 133 } 134 135 136 /** 137 * the search result filter to use for the addition of mandatory operational attributes 138 */ 139 private class OperationalAttributeSearchFilter implements EntryFilter 140 { 141 /** 142 * {@inheritDoc} 143 */ 144 @Override 145 public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException 146 { 147 if ( operation.getReturningAttributesString() == null ) 148 { 149 return true; 150 } 151 152 // Add the SubschemaSubentry AttributeType if it's requested 153 SchemaManager schemaManager = operation.getSession().getDirectoryService().getSchemaManager(); 154 155 if ( operation.isAllOperationalAttributes() 156 || operation.getReturningAttributes().contains( 157 new AttributeTypeOptions( schemaManager.getAttributeType( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ) ) ) ) 158 { 159 AttributeType subschemaSubentryAt = schemaManager.getAttributeType( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ); 160 entry.add( new DefaultAttribute( subschemaSubentryAt, 161 directoryService.getPartitionNexus().getRootDseValue( subschemaSubentryAt ) ) ); 162 } 163 164 return true; 165 } 166 167 168 /** 169 * {@inheritDoc} 170 */ 171 @Override 172 public String toString( String tabs ) 173 { 174 return tabs + "OperationalAttributeSearchFilter"; 175 } 176 } 177 178 179 /** 180 * The search result filter to use for the addition of the subordinates attributes, if requested 181 */ 182 private class SubordinatesSearchFilter implements EntryFilter 183 { 184 /** 185 * {@inheritDoc} 186 */ 187 @Override 188 public boolean accept( SearchOperationContext searchOperationContext, Entry entry ) throws LdapException 189 { 190 // Add the nbChildren/nbSubordinates attributes if required 191 processSubordinates( searchOperationContext, searchOperationContext.getReturningAttributes(), 192 searchOperationContext.isAllOperationalAttributes(), entry ); 193 194 return true; 195 } 196 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override 202 public String toString( String tabs ) 203 { 204 return tabs + "SubordinatesSearchFilter"; 205 } 206 } 207 208 209 /** 210 * Creates the operational attribute management service interceptor. 211 */ 212 public OperationalAttributeInterceptor() 213 { 214 super( InterceptorEnum.OPERATIONAL_ATTRIBUTE_INTERCEPTOR ); 215 } 216 217 218 @Override 219 public void init( DirectoryService directoryService ) throws LdapException 220 { 221 super.init( directoryService ); 222 223 // stuff for dealing with subentries (garbage for now) 224 Value subschemaSubentry = directoryService.getPartitionNexus().getRootDseValue( 225 directoryService.getAtProvider().getSubschemaSubentry() ); 226 subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() ); 227 228 // Create the Admin Dn 229 adminDn = dnFactory.create( ServerDNConstants.ADMIN_SYSTEM_DN ); 230 } 231 232 233 @Override 234 public void destroy() 235 { 236 } 237 238 239 /** 240 * Check if we have to add an operational attribute, or if the admin has injected one 241 */ 242 private boolean checkAddOperationalAttribute( boolean isAdmin, Entry entry, AttributeType attribute ) 243 throws LdapException 244 { 245 if ( entry.containsAttribute( attribute ) ) 246 { 247 if ( !isAdmin ) 248 { 249 // Wrong ! 250 String message = I18n.err( I18n.ERR_30, attribute ); 251 LOG.error( message ); 252 throw new LdapNoPermissionException( message ); 253 } 254 else 255 { 256 return true; 257 } 258 } 259 else 260 { 261 return false; 262 } 263 } 264 265 266 /** 267 * Adds extra operational attributes to the entry before it is added. 268 * 269 * We add those attributes : 270 * - creatorsName 271 * - createTimestamp 272 * - entryCSN 273 * - entryUUID 274 */ 275 /** 276 * {@inheritDoc} 277 */ 278 @Override 279 public void add( AddOperationContext addContext ) throws LdapException 280 { 281 String principal = getPrincipal( addContext ).getName(); 282 283 Entry entry = addContext.getEntry(); 284 285 // If we are using replication, the below four OAs may already be present and we retain 286 // those values if the user is admin. 287 boolean isAdmin = addContext.getSession().getAuthenticatedPrincipal().getDn().equals( adminDn ); 288 289 // The EntryUUID attribute 290 if ( !checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getEntryUUID() ) ) 291 { 292 entry.put( directoryService.getAtProvider().getEntryUUID(), UUID.randomUUID().toString() ); 293 } 294 295 // The EntryCSN attribute 296 if ( !checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getEntryCSN() ) ) 297 { 298 entry.put( directoryService.getAtProvider().getEntryCSN(), directoryService.getCSN().toString() ); 299 } 300 301 // The CreatorsName attribute 302 if ( !checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getCreatorsName() ) ) 303 { 304 entry.put( directoryService.getAtProvider().getCreatorsName(), principal ); 305 } 306 307 // The CreateTimeStamp attribute 308 if ( !checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getCreateTimestamp() ) ) 309 { 310 entry.put( directoryService.getAtProvider().getCreateTimestamp(), DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 311 } 312 313 // Now, check that the user does not add operational attributes 314 // The accessControlSubentries attribute 315 checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getAccessControlSubentries() ); 316 317 // The CollectiveAttributeSubentries attribute 318 checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider() 319 .getCollectiveAttributeSubentries() ); 320 321 // The TriggerExecutionSubentries attribute 322 checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getTriggerExecutionSubentries() ); 323 324 // The SubSchemaSybentry attribute 325 checkAddOperationalAttribute( isAdmin, entry, directoryService.getAtProvider().getSubschemaSubentry() ); 326 327 next( addContext ); 328 } 329 330 331 /** 332 * {@inheritDoc} 333 */ 334 @Override 335 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 336 { 337 Dn dn = lookupContext.getDn(); 338 339 if ( dn.getNormName().equals( subschemaSubentryDn.getNormName() ) ) 340 { 341 Entry serverEntry = SchemaService.getSubschemaEntry( directoryService, lookupContext ); 342 serverEntry.setDn( dn ); 343 344 return serverEntry; 345 } 346 347 Entry entry = next( lookupContext ); 348 349 denormalizeEntryOpAttrs( entry ); 350 351 // Add the nbChildren/nbSubordinates attributes if required 352 processSubordinates( lookupContext, lookupContext.getReturningAttributes(), lookupContext.isAllOperationalAttributes(), entry ); 353 354 return entry; 355 } 356 357 358 /** 359 * {@inheritDoc} 360 */ 361 @Override 362 public void modify( ModifyOperationContext modifyContext ) throws LdapException 363 { 364 // We must check that the user hasn't injected either the modifiersName 365 // or the modifyTimestamp operational attributes : they are not supposed to be 366 // added at this point EXCEPT in cases of replication by a admin user. 367 // If so, remove them, and if there are no more attributes, simply return. 368 // otherwise, inject those values into the list of modifications 369 List<Modification> mods = modifyContext.getModItems(); 370 371 boolean isAdmin = modifyContext.getSession().getAuthenticatedPrincipal().getDn().equals( adminDn ); 372 373 boolean modifierAtPresent = false; 374 boolean modifiedTimeAtPresent = false; 375 boolean entryCsnAtPresent = false; 376 Dn dn = modifyContext.getDn(); 377 378 for ( Modification modification : mods ) 379 { 380 AttributeType attributeType = modification.getAttribute().getAttributeType(); 381 382 if ( attributeType.equals( directoryService.getAtProvider().getModifiersName() ) ) 383 { 384 if ( !isAdmin ) 385 { 386 String message = I18n.err( I18n.ERR_31 ); 387 LOG.error( message ); 388 throw new LdapNoPermissionException( message ); 389 } 390 else 391 { 392 modifierAtPresent = true; 393 } 394 } 395 396 if ( attributeType.equals( directoryService.getAtProvider().getModifyTimestamp() ) ) 397 { 398 if ( !isAdmin ) 399 { 400 String message = I18n.err( I18n.ERR_30, attributeType ); 401 LOG.error( message ); 402 throw new LdapNoPermissionException( message ); 403 } 404 else 405 { 406 modifiedTimeAtPresent = true; 407 } 408 } 409 410 if ( attributeType.equals( directoryService.getAtProvider().getEntryCSN() ) ) 411 { 412 if ( !isAdmin ) 413 { 414 String message = I18n.err( I18n.ERR_30, attributeType ); 415 LOG.error( message ); 416 throw new LdapNoPermissionException( message ); 417 } 418 else 419 { 420 entryCsnAtPresent = true; 421 } 422 } 423 424 if ( PWD_POLICY_STATE_ATTRIBUTE_TYPES.contains( attributeType ) && !isAdmin ) 425 { 426 String message = I18n.err( I18n.ERR_30, attributeType ); 427 LOG.error( message ); 428 throw new LdapNoPermissionException( message ); 429 } 430 } 431 432 // Add the modification AT only if we are not trying to modify the SubentrySubschema 433 if ( !dn.equals( subschemaSubentryDn ) ) 434 { 435 if ( !modifierAtPresent ) 436 { 437 // Inject the ModifiersName AT if it's not present 438 Attribute attribute = new DefaultAttribute( directoryService.getAtProvider().getModifiersName(), 439 getPrincipal( modifyContext ).getName() ); 440 441 Modification modifiersName = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, 442 attribute ); 443 444 mods.add( modifiersName ); 445 } 446 447 if ( !modifiedTimeAtPresent ) 448 { 449 // Inject the ModifyTimestamp AT if it's not present 450 Attribute attribute = new DefaultAttribute( directoryService.getAtProvider().getModifyTimestamp(), 451 DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 452 453 Modification timestamp = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 454 455 mods.add( timestamp ); 456 } 457 458 if ( !entryCsnAtPresent ) 459 { 460 String csn = directoryService.getCSN().toString(); 461 Attribute attribute = new DefaultAttribute( directoryService.getAtProvider().getEntryCSN(), csn ); 462 Modification updatedCsn = new DefaultModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); 463 mods.add( updatedCsn ); 464 } 465 } 466 467 // Go down in the chain 468 next( modifyContext ); 469 } 470 471 472 /** 473 * {@inheritDoc} 474 */ 475 @Override 476 public void move( MoveOperationContext moveContext ) throws LdapException 477 { 478 Entry modifiedEntry = moveContext.getOriginalEntry().clone(); 479 modifiedEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal( moveContext ).getName() ); 480 modifiedEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 481 482 Attribute csnAt = new DefaultAttribute( directoryService.getAtProvider().getEntryCSN(), directoryService 483 .getCSN().toString() ); 484 modifiedEntry.put( csnAt ); 485 486 modifiedEntry.setDn( moveContext.getNewDn() ); 487 moveContext.setModifiedEntry( modifiedEntry ); 488 489 next( moveContext ); 490 } 491 492 493 /** 494 * {@inheritDoc} 495 */ 496 @Override 497 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 498 { 499 Entry modifiedEntry = moveAndRenameContext.getModifiedEntry(); 500 modifiedEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal( moveAndRenameContext ).getName() ); 501 modifiedEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 502 modifiedEntry.setDn( moveAndRenameContext.getNewDn() ); 503 504 Attribute csnAt = new DefaultAttribute( directoryService.getAtProvider().getEntryCSN(), directoryService 505 .getCSN().toString() ); 506 modifiedEntry.put( csnAt ); 507 508 moveAndRenameContext.setModifiedEntry( modifiedEntry ); 509 510 next( moveAndRenameContext ); 511 } 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override 518 public void rename( RenameOperationContext renameContext ) throws LdapException 519 { 520 Entry entry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getClonedEntry(); 521 entry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal( renameContext ).getName() ); 522 entry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 523 524 Entry modifiedEntry = renameContext.getOriginalEntry().clone(); 525 modifiedEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal( renameContext ).getName() ); 526 modifiedEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime( directoryService.getTimeProvider() ) ); 527 528 Attribute csnAt = new DefaultAttribute( directoryService.getAtProvider().getEntryCSN(), directoryService 529 .getCSN().toString() ); 530 modifiedEntry.put( csnAt ); 531 532 renameContext.setModifiedEntry( modifiedEntry ); 533 534 next( renameContext ); 535 } 536 537 538 /** 539 * {@inheritDoc} 540 */ 541 @Override 542 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 543 { 544 EntryFilteringCursor cursor = next( searchContext ); 545 546 if ( searchContext.isAllOperationalAttributes() 547 || ( ( searchContext.getReturningAttributes() != null ) && !searchContext.getReturningAttributes().isEmpty() ) ) 548 { 549 if ( directoryService.isDenormalizeOpAttrsEnabled() ) 550 { 551 cursor.addEntryFilter( denormalizingSearchFilter ); 552 } 553 554 cursor.addEntryFilter( operationalAttributeSearchFilter ); 555 cursor.addEntryFilter( subordinatesSearchFilter ); 556 557 return cursor; 558 } 559 560 return cursor; 561 } 562 563 564 @Override 565 public void delete( DeleteOperationContext deleteContext ) throws LdapException 566 { 567 // insert a new CSN into the entry, this is for replication 568 Entry entry = deleteContext.getEntry(); 569 Attribute csnAt = new DefaultAttribute( directoryService.getAtProvider().getEntryCSN(), directoryService 570 .getCSN().toString() ); 571 entry.put( csnAt ); 572 573 next( deleteContext ); 574 } 575 576 577 private void denormalizeEntryOpAttrs( Entry entry ) throws LdapException 578 { 579 if ( directoryService.isDenormalizeOpAttrsEnabled() ) 580 { 581 Attribute attr = entry.get( SchemaConstants.CREATORS_NAME_AT ); 582 583 if ( attr != null ) 584 { 585 Dn creatorsName = dnFactory.create( attr.getString() ); 586 587 attr.clear(); 588 attr.add( denormalizeTypes( creatorsName ).getName() ); 589 } 590 591 attr = entry.get( SchemaConstants.MODIFIERS_NAME_AT ); 592 593 if ( attr != null ) 594 { 595 Dn modifiersName = dnFactory.create( attr.getString() ); 596 597 attr.clear(); 598 attr.add( denormalizeTypes( modifiersName ).getName() ); 599 } 600 601 attr = entry.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT ); 602 603 if ( attr != null ) 604 { 605 Dn modifiersName = dnFactory.create( attr.getString() ); 606 607 attr.clear(); 608 attr.add( denormalizeTypes( modifiersName ).getName() ); 609 } 610 } 611 } 612 613 614 /** 615 * Does not create a new Dn but alters existing Dn by using the first 616 * short name for an attributeType definition. 617 * 618 * @param dn the normalized distinguished name 619 * @return the distinguished name denormalized 620 * @throws Exception if there are problems denormalizing 621 */ 622 private Dn denormalizeTypes( Dn dn ) throws LdapException 623 { 624 Dn newDn = new Dn( schemaManager ); 625 int size = dn.size(); 626 627 for ( int pos = 0; pos < size; pos++ ) 628 { 629 Rdn rdn = dn.getRdn( size - 1 - pos ); 630 631 if ( rdn.size() == 0 ) 632 { 633 newDn = newDn.add( new Rdn() ); 634 continue; 635 } 636 else if ( rdn.size() == 1 ) 637 { 638 String name = schemaManager.lookupAttributeTypeRegistry( rdn.getNormType() ).getName(); 639 String value = rdn.getValue(); 640 newDn = newDn.add( new Rdn( name, value ) ); 641 continue; 642 } 643 644 // below we only process multi-valued rdns 645 StringBuilder buf = new StringBuilder(); 646 647 for ( Iterator<Ava> atavs = rdn.iterator(); atavs.hasNext(); /**/) 648 { 649 Ava atav = atavs.next(); 650 String type = schemaManager.lookupAttributeTypeRegistry( rdn.getNormType() ).getName(); 651 buf.append( type ).append( '=' ).append( atav.getValue().getString() ); 652 653 if ( atavs.hasNext() ) 654 { 655 buf.append( '+' ); 656 } 657 } 658 659 newDn = newDn.add( new Rdn( buf.toString() ) ); 660 } 661 662 return newDn; 663 } 664 665 666 private void processSubordinates( OperationContext operationContext, Set<AttributeTypeOptions> returningAttributes, 667 boolean allAttributes, Entry entry ) throws LdapException 668 { 669 // Bypass the rootDSE : we won't get the nbChildren and nbSubordiantes for this special entry 670 if ( Dn.isNullOrEmpty( entry.getDn() ) ) 671 { 672 return; 673 } 674 675 // Add the Subordinates AttributeType if it's requested 676 AttributeType nbChildrenAt = directoryService.getAtProvider().getNbChildren(); 677 AttributeTypeOptions nbChildrenAto = new AttributeTypeOptions( nbChildrenAt ); 678 AttributeType nbSubordinatesAt = directoryService.getAtProvider().getNbSubordinates(); 679 AttributeTypeOptions nbSubordinatesAto = new AttributeTypeOptions( nbSubordinatesAt ); 680 AttributeType hasSubordinatesAt = directoryService.getAtProvider().getHasSubordinates(); 681 AttributeTypeOptions hasSubordinatesAto = new AttributeTypeOptions( hasSubordinatesAt ); 682 AttributeType structuralObjectClassAt = directoryService.getAtProvider().getStructuralObjectClass(); 683 AttributeTypeOptions structuralObjectClassAto = new AttributeTypeOptions( structuralObjectClassAt ); 684 685 if ( returningAttributes != null ) 686 { 687 boolean nbChildrenRequested = returningAttributes.contains( nbChildrenAto ) || allAttributes; 688 boolean nbSubordinatesRequested = returningAttributes.contains( nbSubordinatesAto ) || allAttributes; 689 boolean hasSubordinatesRequested = returningAttributes.contains( hasSubordinatesAto ) || allAttributes; 690 boolean structuralObjectClassRequested = returningAttributes.contains( structuralObjectClassAto ) || allAttributes; 691 692 if ( nbChildrenRequested || nbSubordinatesRequested || hasSubordinatesRequested 693 || structuralObjectClassRequested ) 694 { 695 Partition partition = directoryService.getPartitionNexus().getPartition( entry.getDn() ); 696 Subordinates subordinates = partition.getSubordinates( operationContext.getTransaction(), entry ); 697 698 long nbChildren = subordinates.getNbChildren(); 699 long nbSubordinates = subordinates.getNbSubordinates(); 700 701 // Inject the nbChildren OpAttr if needed 702 if ( nbChildrenRequested ) 703 { 704 entry.add( new DefaultAttribute( nbChildrenAt, 705 Long.toString( nbChildren ) ) ); 706 } 707 708 // Inject the nbSubordinates OpAttr if needed 709 if ( nbSubordinatesRequested ) 710 { 711 entry.add( new DefaultAttribute( nbSubordinatesAt, 712 Long.toString( nbSubordinates ) ) ); 713 } 714 715 // Inject the hasSubordinates OpAttr if needed 716 if ( hasSubordinatesRequested ) 717 { 718 if ( nbSubordinates > 0 ) 719 { 720 entry.add( new DefaultAttribute( hasSubordinatesAt, "TRUE" ) ); 721 } 722 else 723 { 724 entry.add( new DefaultAttribute( hasSubordinatesAt, "FALSE" ) ); 725 } 726 } 727 728 // Inject the structuralObjectclass OpAttr if needed 729 if ( structuralObjectClassRequested ) 730 { 731 Attribute objectClasses = entry.get( SchemaConstants.OBJECT_CLASS_AT ); 732 Map<String, ObjectClass> superiors = new HashMap<>(); 733 ObjectClass[] objectClassArray = new ObjectClass[objectClasses.size()]; 734 int nbStructural = 0; 735 736 // First get all the structural objectClasses 737 for ( Value objectClassValue : objectClasses ) 738 { 739 ObjectClass objectClass = 740 schemaManager.getObjectClassRegistry().get( objectClassValue.getNormalized() ); 741 742 if ( objectClass.isStructural() ) 743 { 744 objectClassArray[nbStructural++] = objectClass; 745 746 // We can only have one superior objectClass for Structural ObjectClass 747 superiors.put( objectClass.getSuperiors().get( 0 ).getOid(), objectClass ); 748 } 749 } 750 751 // Then find the top of them 752 if ( nbStructural == 1 ) 753 { 754 entry.add( new DefaultAttribute( structuralObjectClassAt, 755 objectClassArray[0].getName() ) ); 756 } 757 else 758 { 759 ObjectClass topStructural = objectClassArray[0]; 760 761 for ( ObjectClass oc : objectClassArray ) 762 { 763 if ( !superiors.containsKey( oc.getOid() ) ) 764 { 765 // We are done : the current OC is not the superior of any other 766 // OC, this is necessarily the top level one 767 entry.add( new DefaultAttribute( structuralObjectClassAt, oc.getName() ) ); 768 break; 769 } 770 } 771 } 772 } 773 } 774 } 775 } 776}