001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.EnumSet; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.CopyOnWriteArrayList; 029import java.util.concurrent.locks.ReadWriteLock; 030import java.util.concurrent.locks.ReentrantReadWriteLock; 031 032import org.forgerock.i18n.slf4j.LocalizedLogger; 033import org.forgerock.opendj.ldap.DN; 034import org.forgerock.opendj.ldap.ResultCode; 035import org.forgerock.opendj.ldap.SearchScope; 036import org.opends.server.api.Backend; 037import org.opends.server.api.BackendInitializationListener; 038import org.opends.server.api.ClientConnection; 039import org.opends.server.api.DITCacheMap; 040import org.opends.server.api.SubentryChangeListener; 041import org.opends.server.api.plugin.InternalDirectoryServerPlugin; 042import org.opends.server.api.plugin.PluginResult; 043import org.opends.server.api.plugin.PluginResult.PostOperation; 044import org.opends.server.api.plugin.PluginResult.PreOperation; 045import org.opends.server.api.plugin.PluginType; 046import org.opends.server.controls.SubentriesControl; 047import org.opends.server.protocols.internal.InternalClientConnection; 048import org.opends.server.protocols.internal.InternalSearchOperation; 049import org.opends.server.protocols.internal.SearchRequest; 050import org.opends.server.types.DirectoryException; 051import org.opends.server.types.Entry; 052import org.opends.server.types.Privilege; 053import org.opends.server.types.SearchFilter; 054import org.opends.server.types.SearchResultEntry; 055import org.opends.server.types.SubEntry; 056import org.opends.server.types.SubtreeSpecification; 057import org.opends.server.types.operation.PostOperationAddOperation; 058import org.opends.server.types.operation.PostOperationDeleteOperation; 059import org.opends.server.types.operation.PostOperationModifyDNOperation; 060import org.opends.server.types.operation.PostOperationModifyOperation; 061import org.opends.server.types.operation.PostSynchronizationAddOperation; 062import org.opends.server.types.operation.PostSynchronizationDeleteOperation; 063import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; 064import org.opends.server.types.operation.PostSynchronizationModifyOperation; 065import org.opends.server.types.operation.PreOperationAddOperation; 066import org.opends.server.types.operation.PreOperationDeleteOperation; 067import org.opends.server.types.operation.PreOperationModifyDNOperation; 068import org.opends.server.types.operation.PreOperationModifyOperation; 069import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation; 070 071import static org.opends.messages.CoreMessages.*; 072import static org.opends.server.config.ConfigConstants.*; 073import static org.opends.server.protocols.internal.InternalClientConnection.*; 074import static org.opends.server.protocols.internal.Requests.*; 075import static org.opends.server.util.CollectionUtils.*; 076import static org.opends.server.util.ServerConstants.*; 077 078/** 079 * This class provides a mechanism for interacting with subentries defined in 080 * the Directory Server. It will handle all necessary processing at server 081 * startup to identify and load subentries within the server. 082 * <BR><BR> 083 * FIXME: At the present time, it assumes that all of the necessary 084 * information about subentries defined in the server can be held in 085 * memory. If it is determined that this approach is not workable 086 * in all cases, then we will need an alternate strategy. 087 */ 088public class SubentryManager extends InternalDirectoryServerPlugin 089 implements BackendInitializationListener 090{ 091 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 092 093 /** Dummy configuration DN for Subentry Manager. */ 094 private static final String CONFIG_DN = "cn=Subentry Manager,cn=config"; 095 096 /** A mapping between the DNs and applicable subentries. */ 097 private final Map<DN, List<SubEntry>> dn2SubEntry = new HashMap<>(); 098 /** A mapping between the DNs and applicable collective subentries. */ 099 private final Map<DN, List<SubEntry>> dn2CollectiveSubEntry = new HashMap<>(); 100 /** A mapping between subentry DNs and subentry objects. */ 101 private final DITCacheMap<SubEntry> dit2SubEntry = new DITCacheMap<>(); 102 /** Internal search all operational attributes. */ 103 private final Set<String> requestAttrs = newLinkedHashSet("*", "+"); 104 /** Lock to protect internal data structures. */ 105 private final ReadWriteLock lock = new ReentrantReadWriteLock(); 106 /** The set of change notification listeners. */ 107 private final List<SubentryChangeListener> changeListeners = new CopyOnWriteArrayList<>(); 108 109 /** 110 * Creates a new instance of this subentry manager. 111 * 112 * @throws DirectoryException If a problem occurs while 113 * creating an instance of 114 * the subentry manager. 115 */ 116 public SubentryManager() throws DirectoryException 117 { 118 super(DN.valueOf(CONFIG_DN), EnumSet.of( 119 PluginType.PRE_OPERATION_ADD, 120 PluginType.PRE_OPERATION_DELETE, 121 PluginType.PRE_OPERATION_MODIFY, 122 PluginType.PRE_OPERATION_MODIFY_DN, 123 PluginType.POST_OPERATION_ADD, 124 PluginType.POST_OPERATION_DELETE, 125 PluginType.POST_OPERATION_MODIFY, 126 PluginType.POST_OPERATION_MODIFY_DN, 127 PluginType.POST_SYNCHRONIZATION_ADD, 128 PluginType.POST_SYNCHRONIZATION_DELETE, 129 PluginType.POST_SYNCHRONIZATION_MODIFY, 130 PluginType.POST_SYNCHRONIZATION_MODIFY_DN), 131 true); 132 133 DirectoryServer.registerInternalPlugin(this); 134 DirectoryServer.registerBackendInitializationListener(this); 135 } 136 137 /** 138 * Perform any required finalization tasks for Subentry Manager. 139 * This should only be called at Directory Server shutdown. 140 */ 141 public void finalizeSubentryManager() 142 { 143 // Deregister as internal plugin and 144 // backend initialization listener. 145 DirectoryServer.deregisterInternalPlugin(this); 146 DirectoryServer.deregisterBackendInitializationListener(this); 147 } 148 149 /** 150 * Registers the provided change notification listener with this manager 151 * so that it will be notified of any add, delete, modify, or modify DN 152 * operations that are performed. 153 * 154 * @param changeListener The change notification listener to register 155 * with this manager. 156 */ 157 public void registerChangeListener(SubentryChangeListener changeListener) 158 { 159 changeListeners.add(changeListener); 160 } 161 162 /** 163 * Deregisters the provided change notification listener with this manager 164 * so that it will no longer be notified of any add, delete, modify, or 165 * modify DN operations that are performed. 166 * 167 * @param changeListener The change notification listener to deregister 168 * with this manager. 169 */ 170 public void deregisterChangeListener(SubentryChangeListener changeListener) 171 { 172 changeListeners.remove(changeListener); 173 } 174 175 /** 176 * Add a given entry to this subentry manager. 177 * @param entry to add. 178 */ 179 private void addSubentry(Entry entry) throws DirectoryException 180 { 181 SubEntry subEntry = new SubEntry(entry); 182 SubtreeSpecification subSpec = subEntry.getSubTreeSpecification(); 183 DN subDN = subSpec.getBaseDN(); 184 lock.writeLock().lock(); 185 try 186 { 187 Map<DN, List<SubEntry>> subEntryMap = getSubEntryMap(subEntry); 188 List<SubEntry> subList = subEntryMap.get(subDN); 189 if (subList == null) 190 { 191 subList = new ArrayList<>(); 192 subEntryMap.put(subDN, subList); 193 } 194 dit2SubEntry.put(entry.getName(), subEntry); 195 subList.add(subEntry); 196 } 197 finally 198 { 199 lock.writeLock().unlock(); 200 } 201 } 202 203 private Map<DN, List<SubEntry>> getSubEntryMap(SubEntry subEntry) 204 { 205 return (subEntry.isCollective() || subEntry.isInheritedCollective()) ? dn2CollectiveSubEntry : dn2SubEntry; 206 } 207 208 /** 209 * Remove a given entry from this subentry manager. 210 * 211 * @param entry 212 * to remove. 213 */ 214 private void removeSubentry(Entry entry) 215 { 216 lock.writeLock().lock(); 217 try 218 { 219 if (!removeSubEntry(dn2SubEntry, entry)) 220 { 221 removeSubEntry(dn2CollectiveSubEntry, entry); 222 } 223 } 224 finally 225 { 226 lock.writeLock().unlock(); 227 } 228 } 229 230 private boolean removeSubEntry(Map<DN, List<SubEntry>> subEntryMap, Entry entry) 231 { 232 Iterator<List<SubEntry>> subEntryListsIt = subEntryMap.values().iterator(); 233 while (subEntryListsIt.hasNext()) 234 { 235 List<SubEntry> subEntries = subEntryListsIt.next(); 236 Iterator<SubEntry> subEntriesIt = subEntries.iterator(); 237 while (subEntriesIt.hasNext()) 238 { 239 SubEntry subEntry = subEntriesIt.next(); 240 if (subEntry.getDN().equals(entry.getName())) 241 { 242 dit2SubEntry.remove(entry.getName()); 243 subEntriesIt.remove(); 244 if (subEntries.isEmpty()) 245 { 246 subEntryListsIt.remove(); 247 } 248 return true; 249 } 250 } 251 } 252 return false; 253 } 254 255 /** 256 * {@inheritDoc} In this case, the server will search the backend to find 257 * all subentries that it may contain and register them with this manager. 258 */ 259 @Override 260 public void performBackendPreInitializationProcessing(Backend<?> backend) 261 { 262 InternalClientConnection conn = getRootConnection(); 263 SubentriesControl control = new SubentriesControl(true, true); 264 265 SearchFilter filter = null; 266 try 267 { 268 filter = SearchFilter.createFilterFromString("(|" + 269 "(" + ATTR_OBJECTCLASS + "=" + OC_SUBENTRY + ")" + 270 "(" + ATTR_OBJECTCLASS + "=" + OC_LDAP_SUBENTRY + ")" + 271 ")"); 272 if (backend.getEntryCount() > 0 && ! backend.isIndexed(filter)) 273 { 274 logger.warn(WARN_SUBENTRY_FILTER_NOT_INDEXED, filter, backend.getBackendID()); 275 } 276 } 277 catch (Exception e) 278 { 279 logger.traceException(e); 280 } 281 282 for (DN baseDN : backend.getBaseDNs()) 283 { 284 try 285 { 286 if (! backend.entryExists(baseDN)) 287 { 288 continue; 289 } 290 } 291 catch (Exception e) 292 { 293 logger.traceException(e); 294 295 // FIXME -- Is there anything that we need to do here? 296 continue; 297 } 298 299 SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter) 300 .addAttribute(requestAttrs) 301 .addControl(control); 302 InternalSearchOperation internalSearch = 303 new InternalSearchOperation(conn, nextOperationID(), nextMessageID(), request); 304 LocalBackendSearchOperation localSearch = new LocalBackendSearchOperation(internalSearch); 305 306 try 307 { 308 backend.search(localSearch); 309 } 310 catch (Exception e) 311 { 312 logger.traceException(e); 313 314 // FIXME -- Is there anything that we need to do here? 315 continue; 316 } 317 318 for (SearchResultEntry entry : internalSearch.getSearchEntries()) 319 { 320 if (isSubEntry(entry)) 321 { 322 try 323 { 324 addSubentry(entry); 325 notifySubentryAdded(entry); 326 } 327 catch (Exception e) 328 { 329 logger.traceException(e); 330 } 331 } 332 } 333 } 334 } 335 336 private void notifySubentryAdded(final Entry entry) 337 { 338 for (SubentryChangeListener changeListener : changeListeners) 339 { 340 try 341 { 342 changeListener.handleSubentryAdd(entry); 343 } 344 catch (Exception e) 345 { 346 logger.traceException(e); 347 } 348 } 349 } 350 351 private void notifySubentryDeleted(final Entry entry) 352 { 353 for (SubentryChangeListener changeListener : changeListeners) 354 { 355 try 356 { 357 changeListener.handleSubentryDelete(entry); 358 } 359 catch (Exception e) 360 { 361 logger.traceException(e); 362 } 363 } 364 } 365 366 private void notifySubentryModified(final Entry oldEntry, final Entry newEntry) 367 { 368 for (SubentryChangeListener changeListener : changeListeners) 369 { 370 try 371 { 372 changeListener.handleSubentryModify(oldEntry, newEntry); 373 } 374 catch (Exception e) 375 { 376 logger.traceException(e); 377 } 378 } 379 } 380 381 /** 382 * Return all subentries for this manager. 383 * Note that this getter will skip any collective subentries, 384 * returning only applicable regular subentries. 385 * @return all subentries for this manager. 386 */ 387 public List<SubEntry> getSubentries() 388 { 389 if (dn2SubEntry.isEmpty()) 390 { 391 return Collections.emptyList(); 392 } 393 394 List<SubEntry> subentries = new ArrayList<>(); 395 396 lock.readLock().lock(); 397 try 398 { 399 for (List<SubEntry> subList : dn2SubEntry.values()) 400 { 401 subentries.addAll(subList); 402 } 403 } 404 finally 405 { 406 lock.readLock().unlock(); 407 } 408 409 return subentries; 410 } 411 412 /** 413 * Return subentries applicable to specific DN. 414 * Note that this getter will skip any collective subentries, 415 * returning only applicable regular subentries. 416 * @param dn for which to retrieve applicable subentries. 417 * @return applicable subentries. 418 */ 419 public List<SubEntry> getSubentries(DN dn) 420 { 421 return getSubentries(dn2SubEntry, dn); 422 } 423 424 private List<SubEntry> getSubentries(Map<DN, List<SubEntry>> subEntryMap, DN dn) 425 { 426 if (subEntryMap.isEmpty()) 427 { 428 return Collections.emptyList(); 429 } 430 431 lock.readLock().lock(); 432 try 433 { 434 List<SubEntry> subentries = new ArrayList<>(); 435 for (DN subDN = dn; subDN != null && !subDN.isRootDN(); subDN = subDN.parent()) 436 { 437 List<SubEntry> subList = subEntryMap.get(subDN); 438 if (subList != null) 439 { 440 for (SubEntry subEntry : subList) 441 { 442 SubtreeSpecification subSpec = subEntry.getSubTreeSpecification(); 443 if (subSpec.isDNWithinScope(dn)) 444 { 445 subentries.add(subEntry); 446 } 447 } 448 } 449 } 450 return subentries; 451 } 452 finally 453 { 454 lock.readLock().unlock(); 455 } 456 } 457 458 /** 459 * Return subentries applicable to specific entry. 460 * Note that this getter will skip any collective subentries, 461 * returning only applicable regular subentries. 462 * @param entry for which to retrieve applicable 463 * subentries. 464 * @return applicable subentries. 465 */ 466 public List<SubEntry> getSubentries(Entry entry) 467 { 468 return getSubentries(dn2SubEntry, entry); 469 } 470 471 private List<SubEntry> getSubentries(Map<DN, List<SubEntry>> subEntryMap, Entry entry) 472 { 473 if (subEntryMap.isEmpty()) 474 { 475 return Collections.emptyList(); 476 } 477 478 lock.readLock().lock(); 479 try 480 { 481 List<SubEntry> subentries = new ArrayList<>(); 482 for (DN subDN = entry.getName(); subDN != null && !subDN.isRootDN(); subDN = subDN.parent()) 483 { 484 List<SubEntry> subList = subEntryMap.get(subDN); 485 if (subList != null) 486 { 487 for (SubEntry subEntry : subList) 488 { 489 SubtreeSpecification subSpec = subEntry.getSubTreeSpecification(); 490 if (subSpec.isWithinScope(entry)) 491 { 492 subentries.add(subEntry); 493 } 494 } 495 } 496 } 497 return subentries; 498 } 499 finally 500 { 501 lock.readLock().unlock(); 502 } 503 } 504 505 /** 506 * Return collective subentries applicable to specific DN. 507 * Note that this getter will skip any regular subentries, 508 * returning only applicable collective subentries. 509 * @param dn for which to retrieve applicable 510 * subentries. 511 * @return applicable subentries. 512 */ 513 public List<SubEntry> getCollectiveSubentries(DN dn) 514 { 515 return getSubentries(dn2CollectiveSubEntry, dn); 516 } 517 518 /** 519 * Return collective subentries applicable to specific entry. 520 * Note that this getter will skip any regular subentries, 521 * returning only applicable collective subentries. 522 * @param entry for which to retrieve applicable 523 * subentries. 524 * @return applicable subentries. 525 */ 526 public List<SubEntry> getCollectiveSubentries(Entry entry) 527 { 528 return getSubentries(dn2CollectiveSubEntry, entry); 529 } 530 531 /** 532 * {@inheritDoc} In this case, the server will de-register 533 * all subentries associated with the provided backend. 534 */ 535 @Override 536 public void performBackendPostFinalizationProcessing(Backend<?> backend) 537 { 538 lock.writeLock().lock(); 539 try 540 { 541 performBackendPostFinalizationProcessing(dn2SubEntry, backend); 542 performBackendPostFinalizationProcessing(dn2CollectiveSubEntry, backend); 543 } 544 finally 545 { 546 lock.writeLock().unlock(); 547 } 548 } 549 550 private void performBackendPostFinalizationProcessing(Map<DN, List<SubEntry>> subEntryMap, Backend<?> backend) 551 { 552 Iterator<List<SubEntry>> subEntryListsIt = subEntryMap.values().iterator(); 553 while (subEntryListsIt.hasNext()) 554 { 555 List<SubEntry> subEntryList = subEntryListsIt.next(); 556 Iterator<SubEntry> subEntriesIt = subEntryList.iterator(); 557 while (subEntriesIt.hasNext()) 558 { 559 SubEntry subEntry = subEntriesIt.next(); 560 if (backend.handlesEntry(subEntry.getDN())) 561 { 562 dit2SubEntry.remove(subEntry.getDN()); 563 subEntriesIt.remove(); 564 notifySubentryDeleted(subEntry.getEntry()); 565 } 566 } 567 if (subEntryList.isEmpty()) 568 { 569 subEntryListsIt.remove(); 570 } 571 } 572 } 573 574 @Override 575 public void performBackendPostInitializationProcessing(Backend<?> backend) { 576 // Nothing to do. 577 } 578 579 @Override 580 public void performBackendPreFinalizationProcessing(Backend<?> backend) { 581 // Nothing to do. 582 } 583 584 private void doPostAdd(Entry entry) 585 { 586 if (isSubEntry(entry)) 587 { 588 lock.writeLock().lock(); 589 try 590 { 591 try 592 { 593 addSubentry(entry); 594 notifySubentryAdded(entry); 595 } 596 catch (Exception e) 597 { 598 logger.traceException(e); 599 } 600 } 601 finally 602 { 603 lock.writeLock().unlock(); 604 } 605 } 606 } 607 608 private void doPostDelete(Entry entry) 609 { 610 // Fast-path for deleted entries which do not have subordinate sub-entries. 611 lock.readLock().lock(); 612 try 613 { 614 final Collection<SubEntry> subtree = dit2SubEntry.getSubtree(entry.getName()); 615 if (subtree.isEmpty()) 616 { 617 return; 618 } 619 } 620 finally 621 { 622 lock.readLock().unlock(); 623 } 624 625 // Slow-path. 626 lock.writeLock().lock(); 627 try 628 { 629 for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName())) 630 { 631 removeSubentry(subEntry.getEntry()); 632 notifySubentryDeleted(subEntry.getEntry()); 633 } 634 } 635 finally 636 { 637 lock.writeLock().unlock(); 638 } 639 } 640 641 private void doPostModify(Entry oldEntry, Entry newEntry) 642 { 643 final boolean oldEntryIsSubentry = isSubEntry(oldEntry); 644 final boolean newEntryIsSubentry = isSubEntry(newEntry); 645 if (!oldEntryIsSubentry && !newEntryIsSubentry) 646 { 647 return; // Nothing to do. 648 } 649 650 boolean notify = false; 651 lock.writeLock().lock(); 652 try 653 { 654 if (oldEntryIsSubentry) 655 { 656 removeSubentry(oldEntry); 657 notify = true; 658 } 659 if (newEntryIsSubentry) 660 { 661 try 662 { 663 addSubentry(newEntry); 664 notify = true; 665 } 666 catch (Exception e) 667 { 668 logger.traceException(e); 669 670 // FIXME -- Handle this. 671 } 672 } 673 674 if (notify) 675 { 676 notifySubentryModified(oldEntry, newEntry); 677 } 678 } 679 finally 680 { 681 lock.writeLock().unlock(); 682 } 683 } 684 685 private boolean isSubEntry(final Entry e) 686 { 687 return e.isSubentry() || e.isLDAPSubentry(); 688 } 689 690 private void doPostModifyDN(final Entry oldEntry, final Entry newEntry) 691 { 692 lock.writeLock().lock(); 693 try 694 { 695 Collection<SubEntry> setToDelete = dit2SubEntry.getSubtree(oldEntry.getName()); 696 for (SubEntry subentry : setToDelete) 697 { 698 final Entry currentSubentry = subentry.getEntry(); 699 removeSubentry(currentSubentry); 700 701 Entry renamedSubentry = null; 702 try 703 { 704 renamedSubentry = currentSubentry.duplicate(false); 705 final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName()); 706 renamedSubentry.setDN(renamedDN); 707 addSubentry(renamedSubentry); 708 } 709 catch (Exception e) 710 { 711 // Shouldnt happen. 712 logger.traceException(e); 713 } 714 715 notifySubentryModified(currentSubentry, renamedSubentry); 716 } 717 } 718 finally 719 { 720 lock.writeLock().unlock(); 721 } 722 } 723 724 @Override 725 public PreOperation doPreOperation(PreOperationAddOperation addOperation) 726 { 727 Entry entry = addOperation.getEntryToAdd(); 728 729 if (isSubEntry(entry)) 730 { 731 ClientConnection conn = addOperation.getClientConnection(); 732 if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, conn.getOperationInProgress(addOperation.getMessageID()))) 733 { 734 return PluginResult.PreOperation.stopProcessing( 735 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 736 ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); 737 } 738 for (SubentryChangeListener changeListener : changeListeners) 739 { 740 try 741 { 742 changeListener.checkSubentryAddAcceptable(entry); 743 } 744 catch (DirectoryException de) 745 { 746 logger.traceException(de); 747 return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject()); 748 } 749 } 750 } 751 752 return PluginResult.PreOperation.continueOperationProcessing(); 753 } 754 755 @Override 756 public PreOperation doPreOperation(PreOperationDeleteOperation deleteOperation) 757 { 758 Entry entry = deleteOperation.getEntryToDelete(); 759 boolean hasSubentryWritePrivilege = false; 760 761 lock.readLock().lock(); 762 try 763 { 764 for (SubEntry subEntry : dit2SubEntry.getSubtree(entry.getName())) 765 { 766 if (!hasSubentryWritePrivilege) 767 { 768 ClientConnection conn = deleteOperation.getClientConnection(); 769 if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, 770 conn.getOperationInProgress(deleteOperation.getMessageID()))) 771 { 772 return PluginResult.PreOperation.stopProcessing( 773 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 774 ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); 775 } 776 hasSubentryWritePrivilege = true; 777 } 778 for (SubentryChangeListener changeListener : changeListeners) 779 { 780 try 781 { 782 changeListener.checkSubentryDeleteAcceptable(subEntry.getEntry()); 783 } 784 catch (DirectoryException de) 785 { 786 logger.traceException(de); 787 return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject()); 788 } 789 } 790 } 791 } 792 finally 793 { 794 lock.readLock().unlock(); 795 } 796 797 return PluginResult.PreOperation.continueOperationProcessing(); 798 } 799 800 @Override 801 public PreOperation doPreOperation(PreOperationModifyOperation modifyOperation) 802 { 803 Entry oldEntry = modifyOperation.getCurrentEntry(); 804 Entry newEntry = modifyOperation.getModifiedEntry(); 805 806 if (isSubEntry(newEntry) || isSubEntry(oldEntry)) 807 { 808 ClientConnection conn = modifyOperation.getClientConnection(); 809 if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, 810 conn.getOperationInProgress(modifyOperation.getMessageID()))) 811 { 812 return PluginResult.PreOperation.stopProcessing( 813 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 814 ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); 815 } 816 for (SubentryChangeListener changeListener : changeListeners) 817 { 818 try 819 { 820 changeListener.checkSubentryModifyAcceptable(oldEntry, newEntry); 821 } 822 catch (DirectoryException de) 823 { 824 logger.traceException(de); 825 return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject()); 826 } 827 } 828 } 829 830 return PluginResult.PreOperation.continueOperationProcessing(); 831 } 832 833 @Override 834 public PreOperation doPreOperation(PreOperationModifyDNOperation modifyDNOperation) 835 { 836 boolean hasSubentryWritePrivilege = false; 837 838 lock.readLock().lock(); 839 try 840 { 841 final Entry oldEntry = modifyDNOperation.getOriginalEntry(); 842 Collection<SubEntry> setToDelete = dit2SubEntry.getSubtree(oldEntry.getName()); 843 for (SubEntry subentry : setToDelete) 844 { 845 if (!hasSubentryWritePrivilege) 846 { 847 ClientConnection conn = modifyDNOperation.getClientConnection(); 848 if (!conn.hasPrivilege(Privilege.SUBENTRY_WRITE, 849 conn.getOperationInProgress(modifyDNOperation.getMessageID()))) 850 { 851 return PluginResult.PreOperation.stopProcessing( 852 ResultCode.INSUFFICIENT_ACCESS_RIGHTS, 853 ERR_SUBENTRY_WRITE_INSUFFICIENT_PRIVILEGES.get()); 854 } 855 hasSubentryWritePrivilege = true; 856 } 857 858 final Entry newEntry = modifyDNOperation.getUpdatedEntry(); 859 final Entry currentSubentry = subentry.getEntry(); 860 final Entry renamedSubentry = currentSubentry.duplicate(false); 861 final DN renamedDN = currentSubentry.getName().rename(oldEntry.getName(), newEntry.getName()); 862 renamedSubentry.setDN(renamedDN); 863 864 for (SubentryChangeListener changeListener : changeListeners) 865 { 866 try 867 { 868 changeListener.checkSubentryModifyAcceptable(currentSubentry, renamedSubentry); 869 } 870 catch (DirectoryException de) 871 { 872 logger.traceException(de); 873 return PluginResult.PreOperation.stopProcessing(de.getResultCode(), de.getMessageObject()); 874 } 875 } 876 } 877 } 878 finally 879 { 880 lock.readLock().unlock(); 881 } 882 883 return PluginResult.PreOperation.continueOperationProcessing(); 884 } 885 886 @Override 887 public PostOperation doPostOperation(PostOperationAddOperation addOperation) 888 { 889 // Only do something if the operation is successful, meaning there 890 // has been a change. 891 if (addOperation.getResultCode() == ResultCode.SUCCESS) 892 { 893 doPostAdd(addOperation.getEntryToAdd()); 894 } 895 896 // If we've gotten here, then everything is acceptable. 897 return PluginResult.PostOperation.continueOperationProcessing(); 898 } 899 900 @Override 901 public PostOperation doPostOperation(PostOperationDeleteOperation deleteOperation) 902 { 903 // Only do something if the operation is successful, meaning there 904 // has been a change. 905 if (deleteOperation.getResultCode() == ResultCode.SUCCESS) 906 { 907 doPostDelete(deleteOperation.getEntryToDelete()); 908 } 909 910 // If we've gotten here, then everything is acceptable. 911 return PluginResult.PostOperation.continueOperationProcessing(); 912 } 913 914 @Override 915 public PostOperation doPostOperation(PostOperationModifyOperation modifyOperation) 916 { 917 // Only do something if the operation is successful, meaning there 918 // has been a change. 919 if (modifyOperation.getResultCode() == ResultCode.SUCCESS) 920 { 921 doPostModify(modifyOperation.getCurrentEntry(), modifyOperation.getModifiedEntry()); 922 } 923 924 // If we've gotten here, then everything is acceptable. 925 return PluginResult.PostOperation.continueOperationProcessing(); 926 } 927 928 @Override 929 public PostOperation doPostOperation(PostOperationModifyDNOperation modifyDNOperation) 930 { 931 // Only do something if the operation is successful, meaning there 932 // has been a change. 933 if (modifyDNOperation.getResultCode() == ResultCode.SUCCESS) 934 { 935 doPostModifyDN(modifyDNOperation.getOriginalEntry(), modifyDNOperation.getUpdatedEntry()); 936 } 937 938 // If we've gotten here, then everything is acceptable. 939 return PluginResult.PostOperation.continueOperationProcessing(); 940 } 941 942 @Override 943 public void doPostSynchronization(PostSynchronizationAddOperation addOperation) 944 { 945 Entry entry = addOperation.getEntryToAdd(); 946 if (entry != null) 947 { 948 doPostAdd(entry); 949 } 950 } 951 952 @Override 953 public void doPostSynchronization(PostSynchronizationDeleteOperation deleteOperation) 954 { 955 Entry entry = deleteOperation.getEntryToDelete(); 956 if (entry != null) 957 { 958 doPostDelete(entry); 959 } 960 } 961 962 @Override 963 public void doPostSynchronization(PostSynchronizationModifyOperation modifyOperation) 964 { 965 Entry entry = modifyOperation.getCurrentEntry(); 966 Entry modEntry = modifyOperation.getModifiedEntry(); 967 if (entry != null && modEntry != null) 968 { 969 doPostModify(entry, modEntry); 970 } 971 } 972 973 @Override 974 public void doPostSynchronization(PostSynchronizationModifyDNOperation modifyDNOperation) 975 { 976 Entry oldEntry = modifyDNOperation.getOriginalEntry(); 977 Entry newEntry = modifyDNOperation.getUpdatedEntry(); 978 if (oldEntry != null && newEntry != null) 979 { 980 doPostModifyDN(oldEntry, newEntry); 981 } 982 } 983}