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 2007-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2011-2016 ForgeRock AS. 016 */ 017package org.opends.server.core; 018 019import static org.opends.messages.ConfigMessages.*; 020import static org.opends.messages.CoreMessages.*; 021import static org.opends.server.protocols.internal.InternalClientConnection.*; 022import static org.opends.server.protocols.internal.Requests.*; 023import static org.opends.server.util.ServerConstants.*; 024import static org.opends.server.util.StaticUtils.*; 025 026import java.util.ArrayList; 027import java.util.EnumSet; 028import java.util.HashSet; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Map; 032import java.util.Set; 033import java.util.concurrent.ConcurrentHashMap; 034import java.util.concurrent.ConcurrentMap; 035import java.util.concurrent.locks.ReadWriteLock; 036import java.util.concurrent.locks.ReentrantReadWriteLock; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.forgerock.i18n.slf4j.LocalizedLogger; 040import org.forgerock.opendj.config.server.ConfigChangeResult; 041import org.forgerock.opendj.config.server.ConfigException; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.forgerock.opendj.ldap.SearchScope; 044import org.forgerock.util.Utils; 045import org.forgerock.opendj.config.ClassPropertyDefinition; 046import org.forgerock.opendj.config.server.ConfigurationAddListener; 047import org.forgerock.opendj.config.server.ConfigurationChangeListener; 048import org.forgerock.opendj.config.server.ConfigurationDeleteListener; 049import org.forgerock.opendj.server.config.meta.GroupImplementationCfgDefn; 050import org.forgerock.opendj.server.config.server.GroupImplementationCfg; 051import org.forgerock.opendj.server.config.server.RootCfg; 052import org.opends.server.api.Backend; 053import org.opends.server.api.BackendInitializationListener; 054import org.opends.server.api.DITCacheMap; 055import org.opends.server.api.Group; 056import org.opends.server.api.plugin.InternalDirectoryServerPlugin; 057import org.opends.server.api.plugin.PluginResult; 058import org.opends.server.api.plugin.PluginResult.PostOperation; 059import org.opends.server.api.plugin.PluginType; 060import org.opends.server.protocols.internal.InternalClientConnection; 061import org.opends.server.protocols.internal.InternalSearchOperation; 062import org.opends.server.protocols.internal.SearchRequest; 063import org.opends.server.protocols.ldap.LDAPControl; 064import org.opends.server.types.Control; 065import org.forgerock.opendj.ldap.DN; 066import org.opends.server.types.DirectoryException; 067import org.opends.server.types.Entry; 068import org.opends.server.types.InitializationException; 069import org.opends.server.types.Modification; 070import org.opends.server.types.SearchFilter; 071import org.opends.server.types.SearchResultEntry; 072import org.opends.server.types.operation.PluginOperation; 073import org.opends.server.types.operation.PostOperationAddOperation; 074import org.opends.server.types.operation.PostOperationDeleteOperation; 075import org.opends.server.types.operation.PostOperationModifyDNOperation; 076import org.opends.server.types.operation.PostOperationModifyOperation; 077import org.opends.server.types.operation.PostSynchronizationAddOperation; 078import org.opends.server.types.operation.PostSynchronizationDeleteOperation; 079import org.opends.server.types.operation.PostSynchronizationModifyDNOperation; 080import org.opends.server.types.operation.PostSynchronizationModifyOperation; 081import org.opends.server.workflowelement.localbackend.LocalBackendSearchOperation; 082 083/** 084 * This class provides a mechanism for interacting with all groups defined in 085 * the Directory Server. It will handle all necessary processing at server 086 * startup to identify and load all group implementations, as well as to find 087 * all group instances within the server. 088 * <BR><BR> 089 * FIXME: At the present time, it assumes that all of the necessary 090 * information about all of the groups defined in the server can be held in 091 * memory. If it is determined that this approach is not workable in all cases, 092 * then we will need an alternate strategy. 093 */ 094public class GroupManager extends InternalDirectoryServerPlugin 095 implements ConfigurationChangeListener<GroupImplementationCfg>, 096 ConfigurationAddListener<GroupImplementationCfg>, 097 ConfigurationDeleteListener<GroupImplementationCfg>, 098 BackendInitializationListener 099{ 100 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 101 102 /** Used by group instances to determine if new groups have been registered or groups deleted. */ 103 private volatile long refreshToken; 104 105 /** A mapping between the DNs of the config entries and the associated group implementations. */ 106 private ConcurrentMap<DN, Group<?>> groupImplementations; 107 108 /** A mapping between the DNs of all group entries and the corresponding group instances. */ 109 private DITCacheMap<Group<?>> groupInstances; 110 111 /** Lock to protect internal data structures. */ 112 private final ReadWriteLock lock; 113 114 /** Dummy configuration DN for Group Manager. */ 115 private static final String CONFIG_DN = "cn=Group Manager,cn=config"; 116 117 private final ServerContext serverContext; 118 119 /** 120 * Creates a new instance of this group manager. 121 * 122 * @param serverContext 123 * The server context. 124 * @throws DirectoryException 125 * If a problem occurs while creating an instance of the group 126 * manager. 127 */ 128 public GroupManager(ServerContext serverContext) throws DirectoryException 129 { 130 super(DN.valueOf(CONFIG_DN), EnumSet.of(PluginType.POST_OPERATION_ADD, 131 PluginType.POST_OPERATION_DELETE, PluginType.POST_OPERATION_MODIFY, 132 PluginType.POST_OPERATION_MODIFY_DN, 133 PluginType.POST_SYNCHRONIZATION_ADD, 134 PluginType.POST_SYNCHRONIZATION_DELETE, 135 PluginType.POST_SYNCHRONIZATION_MODIFY, 136 PluginType.POST_SYNCHRONIZATION_MODIFY_DN), true); 137 this.serverContext = serverContext; 138 139 groupImplementations = new ConcurrentHashMap<>(); 140 groupInstances = new DITCacheMap<>(); 141 142 lock = new ReentrantReadWriteLock(); 143 144 DirectoryServer.registerInternalPlugin(this); 145 DirectoryServer.registerBackendInitializationListener(this); 146 } 147 148 /** 149 * Initializes all group implementations currently defined in the Directory 150 * Server configuration. This should only be called at Directory Server 151 * startup. 152 * 153 * @throws ConfigException If a configuration problem causes the group 154 * implementation initialization process to fail. 155 * 156 * @throws InitializationException If a problem occurs while initializing 157 * the group implementations that is not 158 * related to the server configuration. 159 */ 160 public void initializeGroupImplementations() 161 throws ConfigException, InitializationException 162 { 163 RootCfg rootConfiguration = serverContext.getRootConfig(); 164 rootConfiguration.addGroupImplementationAddListener(this); 165 rootConfiguration.addGroupImplementationDeleteListener(this); 166 167 //Initialize the existing group implementations. 168 for (String name : rootConfiguration.listGroupImplementations()) 169 { 170 GroupImplementationCfg groupConfiguration = 171 rootConfiguration.getGroupImplementation(name); 172 groupConfiguration.addChangeListener(this); 173 174 if (groupConfiguration.isEnabled()) 175 { 176 try 177 { 178 Group<?> group = loadGroup(groupConfiguration.getJavaClass(), groupConfiguration, true); 179 groupImplementations.put(groupConfiguration.dn(), group); 180 } 181 catch (InitializationException ie) 182 { 183 // Log error but keep going 184 logger.error(ie.getMessageObject()); 185 } 186 } 187 } 188 } 189 190 @Override 191 public boolean isConfigurationAddAcceptable( 192 GroupImplementationCfg configuration, 193 List<LocalizableMessage> unacceptableReasons) 194 { 195 if (configuration.isEnabled()) 196 { 197 try 198 { 199 loadGroup(configuration.getJavaClass(), configuration, false); 200 } 201 catch (InitializationException ie) 202 { 203 unacceptableReasons.add(ie.getMessageObject()); 204 return false; 205 } 206 } 207 return true; 208 } 209 210 @Override 211 public ConfigChangeResult applyConfigurationAdd( 212 GroupImplementationCfg configuration) 213 { 214 final ConfigChangeResult ccr = new ConfigChangeResult(); 215 216 configuration.addChangeListener(this); 217 218 if (! configuration.isEnabled()) 219 { 220 return ccr; 221 } 222 223 Group<?> group = null; 224 try 225 { 226 group = loadGroup(configuration.getJavaClass(), configuration, true); 227 } 228 catch (InitializationException ie) 229 { 230 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 231 ccr.addMessage(ie.getMessageObject()); 232 } 233 234 if (ccr.getResultCode() == ResultCode.SUCCESS) 235 { 236 groupImplementations.put(configuration.dn(), group); 237 } 238 239 // FIXME -- We need to make sure to find all groups of this type in the 240 // server before returning. 241 return ccr; 242 } 243 244 @Override 245 public boolean isConfigurationDeleteAcceptable( 246 GroupImplementationCfg configuration, 247 List<LocalizableMessage> unacceptableReasons) 248 { 249 // FIXME -- We should try to perform some check to determine whether the 250 // group implementation is in use. 251 return true; 252 } 253 254 @Override 255 public ConfigChangeResult applyConfigurationDelete( 256 GroupImplementationCfg configuration) 257 { 258 final ConfigChangeResult ccr = new ConfigChangeResult(); 259 260 Group<?> group = groupImplementations.remove(configuration.dn()); 261 if (group != null) 262 { 263 lock.writeLock().lock(); 264 try 265 { 266 Iterator<Group<?>> iterator = groupInstances.values().iterator(); 267 while (iterator.hasNext()) 268 { 269 Group<?> g = iterator.next(); 270 if (g.getClass().getName().equals(group.getClass().getName())) 271 { 272 iterator.remove(); 273 } 274 } 275 } 276 finally 277 { 278 lock.writeLock().unlock(); 279 } 280 281 group.finalizeGroupImplementation(); 282 } 283 284 return ccr; 285 } 286 287 @Override 288 public boolean isConfigurationChangeAcceptable( 289 GroupImplementationCfg configuration, 290 List<LocalizableMessage> unacceptableReasons) 291 { 292 if (configuration.isEnabled()) 293 { 294 try 295 { 296 loadGroup(configuration.getJavaClass(), configuration, false); 297 } 298 catch (InitializationException ie) 299 { 300 unacceptableReasons.add(ie.getMessageObject()); 301 return false; 302 } 303 } 304 return true; 305 } 306 307 @Override 308 public ConfigChangeResult applyConfigurationChange( 309 GroupImplementationCfg configuration) 310 { 311 final ConfigChangeResult ccr = new ConfigChangeResult(); 312 // Get the existing group implementation if it's already enabled. 313 Group<?> existingGroup = groupImplementations.get(configuration.dn()); 314 315 // If the new configuration has the group implementation disabled, then 316 // disable it if it is enabled, or do nothing if it's already disabled. 317 if (! configuration.isEnabled()) 318 { 319 if (existingGroup != null) 320 { 321 Group<?> group = groupImplementations.remove(configuration.dn()); 322 if (group != null) 323 { 324 lock.writeLock().lock(); 325 try 326 { 327 Iterator<Group<?>> iterator = groupInstances.values().iterator(); 328 while (iterator.hasNext()) 329 { 330 Group<?> g = iterator.next(); 331 if (g.getClass().getName().equals(group.getClass().getName())) 332 { 333 iterator.remove(); 334 } 335 } 336 } 337 finally 338 { 339 lock.writeLock().unlock(); 340 } 341 342 group.finalizeGroupImplementation(); 343 } 344 } 345 346 return ccr; 347 } 348 349 // Get the class for the group implementation. If the group is already 350 // enabled, then we shouldn't do anything with it although if the class has 351 // changed then we'll at least need to indicate that administrative action 352 // is required. If the group implementation is disabled, then instantiate 353 // the class and initialize and register it as a group implementation. 354 String className = configuration.getJavaClass(); 355 if (existingGroup != null) 356 { 357 if (! className.equals(existingGroup.getClass().getName())) 358 { 359 ccr.setAdminActionRequired(true); 360 } 361 362 return ccr; 363 } 364 365 Group<?> group = null; 366 try 367 { 368 group = loadGroup(className, configuration, true); 369 } 370 catch (InitializationException ie) 371 { 372 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 373 ccr.addMessage(ie.getMessageObject()); 374 } 375 376 if (ccr.getResultCode() == ResultCode.SUCCESS) 377 { 378 groupImplementations.put(configuration.dn(), group); 379 } 380 381 // FIXME -- We need to make sure to find all groups of this type in the 382 // server before returning. 383 return ccr; 384 } 385 386 /** 387 * Loads the specified class, instantiates it as a group implementation, and 388 * optionally initializes that instance. 389 * 390 * @param className The fully-qualified name of the group implementation 391 * class to load, instantiate, and initialize. 392 * @param configuration The configuration to use to initialize the group 393 * implementation. It must not be {@code null}. 394 * @param initialize Indicates whether the group implementation instance 395 * should be initialized. 396 * 397 * @return The possibly initialized group implementation. 398 * 399 * @throws InitializationException If a problem occurred while attempting to 400 * initialize the group implementation. 401 */ 402 private static Group<?> loadGroup(String className, 403 GroupImplementationCfg configuration, 404 boolean initialize) 405 throws InitializationException 406 { 407 try 408 { 409 GroupImplementationCfgDefn definition = 410 GroupImplementationCfgDefn.getInstance(); 411 ClassPropertyDefinition propertyDefinition = 412 definition.getJavaClassPropertyDefinition(); 413 Class<? extends Group> groupClass = 414 propertyDefinition.loadClass(className, Group.class); 415 Group group = groupClass.newInstance(); 416 417 if (initialize) 418 { 419 group.initializeGroupImplementation(configuration); 420 } 421 else 422 { 423 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 424 if (!group.isConfigurationAcceptable(configuration, unacceptableReasons)) 425 { 426 String reason = Utils.joinAsString(". ", unacceptableReasons); 427 throw new InitializationException(ERR_CONFIG_GROUP_CONFIG_NOT_ACCEPTABLE.get( 428 configuration.dn(), reason)); 429 } 430 } 431 432 return group; 433 } 434 catch (Exception e) 435 { 436 LocalizableMessage message = ERR_CONFIG_GROUP_INITIALIZATION_FAILED. 437 get(className, configuration.dn(), stackTraceToSingleLineString(e)); 438 throw new InitializationException(message, e); 439 } 440 } 441 442 /** Performs any cleanup work that may be needed when the server is shutting down. */ 443 public void finalizeGroupManager() 444 { 445 DirectoryServer.deregisterInternalPlugin(this); 446 DirectoryServer.deregisterBackendInitializationListener(this); 447 448 deregisterAllGroups(); 449 450 for (Group<?> groupImplementation : groupImplementations.values()) 451 { 452 groupImplementation.finalizeGroupImplementation(); 453 } 454 455 groupImplementations.clear(); 456 } 457 458 /** 459 * Retrieves an {@code Iterable} object that may be used to cursor across the 460 * group implementations defined in the server. 461 * 462 * @return An {@code Iterable} object that may be used to cursor across the 463 * group implementations defined in the server. 464 */ 465 public Iterable<Group<?>> getGroupImplementations() 466 { 467 return groupImplementations.values(); 468 } 469 470 /** 471 * Retrieves an {@code Iterable} object that may be used to cursor across the 472 * group instances defined in the server. 473 * 474 * @return An {@code Iterable} object that may be used to cursor across the 475 * group instances defined in the server. 476 */ 477 public Iterable<Group<?>> getGroupInstances() 478 { 479 lock.readLock().lock(); 480 try 481 { 482 // Return a copy to protect from structural changes. 483 return new ArrayList<>(groupInstances.values()); 484 } 485 finally 486 { 487 lock.readLock().unlock(); 488 } 489 } 490 491 /** 492 * Retrieves the group instance defined in the entry with the specified DN. 493 * 494 * @param entryDN The DN of the entry containing the definition of the group 495 * instance to retrieve. 496 * 497 * @return The group instance defined in the entry with the specified DN, or 498 * {@code null} if no such group is currently defined. 499 */ 500 public Group<?> getGroupInstance(DN entryDN) 501 { 502 lock.readLock().lock(); 503 try 504 { 505 return groupInstances.get(entryDN); 506 } 507 finally 508 { 509 lock.readLock().unlock(); 510 } 511 } 512 513 /** 514 * {@inheritDoc} In this case, the server will search the backend to find 515 * all group instances that it may contain and register them with this group 516 * manager. 517 */ 518 @Override 519 public void performBackendPreInitializationProcessing(Backend<?> backend) 520 { 521 InternalClientConnection conn = getRootConnection(); 522 523 LDAPControl control = new LDAPControl(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE, false); 524 for (DN configEntryDN : groupImplementations.keySet()) 525 { 526 SearchFilter filter; 527 Group<?> groupImplementation = groupImplementations.get(configEntryDN); 528 try 529 { 530 filter = groupImplementation.getGroupDefinitionFilter(); 531 if (backend.getEntryCount() > 0 && ! backend.isIndexed(filter)) 532 { 533 logger.warn(WARN_GROUP_FILTER_NOT_INDEXED, filter, configEntryDN, backend.getBackendID()); 534 } 535 } 536 catch (Exception e) 537 { 538 logger.traceException(e); 539 continue; 540 } 541 542 for (DN baseDN : backend.getBaseDNs()) 543 { 544 try 545 { 546 if (! backend.entryExists(baseDN)) 547 { 548 continue; 549 } 550 } 551 catch (Exception e) 552 { 553 logger.traceException(e); 554 continue; 555 } 556 557 SearchRequest request = newSearchRequest(baseDN, SearchScope.WHOLE_SUBTREE, filter) 558 .addControl(control); 559 InternalSearchOperation internalSearch = 560 new InternalSearchOperation(conn, nextOperationID(), nextMessageID(), request); 561 LocalBackendSearchOperation localSearch = 562 new LocalBackendSearchOperation(internalSearch); 563 try 564 { 565 backend.search(localSearch); 566 } 567 catch (Exception e) 568 { 569 logger.traceException(e); 570 571 // FIXME -- Is there anything that we need to do here? 572 continue; 573 } 574 575 lock.writeLock().lock(); 576 try 577 { 578 for (SearchResultEntry entry : internalSearch.getSearchEntries()) 579 { 580 try 581 { 582 Group<?> groupInstance = groupImplementation.newInstance(serverContext, entry); 583 groupInstances.put(entry.getName(), groupInstance); 584 refreshToken++; 585 } 586 catch (DirectoryException e) 587 { 588 logger.traceException(e); 589 // Nothing specific to do, as it's already logged. 590 } 591 } 592 } 593 finally 594 { 595 lock.writeLock().unlock(); 596 } 597 } 598 } 599 } 600 601 /** 602 * {@inheritDoc} In this case, the server will de-register all group 603 * instances associated with entries in the provided backend. 604 */ 605 @Override 606 public void performBackendPostFinalizationProcessing(Backend<?> backend) 607 { 608 lock.writeLock().lock(); 609 try 610 { 611 Iterator<Map.Entry<DN, Group<?>>> iterator = groupInstances.entrySet().iterator(); 612 while (iterator.hasNext()) 613 { 614 Map.Entry<DN, Group<?>> mapEntry = iterator.next(); 615 DN groupEntryDN = mapEntry.getKey(); 616 if (backend.handlesEntry(groupEntryDN)) 617 { 618 iterator.remove(); 619 } 620 } 621 } 622 finally 623 { 624 lock.writeLock().unlock(); 625 } 626 } 627 628 @Override 629 public void performBackendPostInitializationProcessing(Backend<?> backend) { 630 // Nothing to do. 631 } 632 633 @Override 634 public void performBackendPreFinalizationProcessing(Backend<?> backend) { 635 // Nothing to do. 636 } 637 638 /** 639 * In this case, each entry is checked to see if it contains 640 * a group definition, and if so it will be instantiated and 641 * registered with this group manager. 642 */ 643 private void doPostAdd(PluginOperation addOperation, Entry entry) 644 { 645 if (hasGroupMembershipUpdateControl(addOperation)) 646 { 647 return; 648 } 649 650 createAndRegisterGroup(entry); 651 } 652 653 private static boolean hasGroupMembershipUpdateControl(PluginOperation operation) 654 { 655 List<Control> requestControls = operation.getRequestControls(); 656 if (requestControls != null) 657 { 658 for (Control c : requestControls) 659 { 660 if (OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE.equals(c.getOID())) 661 { 662 return true; 663 } 664 } 665 } 666 return false; 667 } 668 669 /** 670 * In this case, if the entry is associated with a registered 671 * group instance, then that group instance will be deregistered. 672 */ 673 private void doPostDelete(PluginOperation deleteOperation, Entry entry) 674 { 675 if (hasGroupMembershipUpdateControl(deleteOperation)) 676 { 677 return; 678 } 679 680 lock.writeLock().lock(); 681 try 682 { 683 if (groupInstances.removeSubtree(entry.getName(), null)) 684 { 685 refreshToken++; 686 } 687 } 688 finally 689 { 690 lock.writeLock().unlock(); 691 } 692 } 693 694 /** 695 * Scan the list of provided modifications looking for any changes to the objectClass, 696 * which might change the entry to another kind of group, or even to a non-group. 697 * 698 * @param modifications List of modifications to the current group 699 * 700 * @return {@code true} if the objectClass is changed in any way, {@code false} otherwise. 701 */ 702 private boolean updatesObjectClass(List<Modification> modifications) 703 { 704 for (Modification mod : modifications) 705 { 706 if (mod.getAttribute().getAttributeDescription().getAttributeType().isObjectClass()) 707 { 708 return true; 709 } 710 } 711 return false; 712 } 713 714 /** 715 * In this case, if the entry is associated with a registered 716 * group instance, then that instance will be recreated from 717 * the contents of the provided entry and re-registered with 718 * the group manager. 719 */ 720 private void doPostModify(PluginOperation modifyOperation, 721 Entry oldEntry, Entry newEntry, 722 List<Modification> modifications) 723 { 724 if (hasGroupMembershipUpdateControl(modifyOperation)) 725 { 726 return; 727 } 728 729 lock.readLock().lock(); 730 try 731 { 732 if (!groupInstances.containsKey(oldEntry.getName())) 733 { 734 // If the modified entry is not in any group instance, it's probably 735 // not a group, exit fast 736 return; 737 } 738 } 739 finally 740 { 741 lock.readLock().unlock(); 742 } 743 744 lock.writeLock().lock(); 745 try 746 { 747 Group<?> group = groupInstances.get(oldEntry.getName()); 748 if (group != null) 749 { 750 if (!oldEntry.getName().equals(newEntry.getName()) 751 || !group.mayAlterMemberList() 752 || updatesObjectClass(modifications)) 753 { 754 groupInstances.remove(oldEntry.getName()); 755 // This updates the refreshToken 756 createAndRegisterGroup(newEntry); 757 } 758 else 759 { 760 group.updateMembers(modifications); 761 } 762 } 763 } 764 catch (UnsupportedOperationException | DirectoryException e) 765 { 766 logger.traceException(e); 767 } 768 finally 769 { 770 lock.writeLock().unlock(); 771 } 772 } 773 774 /** 775 * In this case, if the entry is associated with a registered 776 * group instance, then that instance will be recreated from 777 * the contents of the provided entry and re-registered with 778 * the group manager under the new DN, and the old instance 779 * will be deregistered. 780 */ 781 private void doPostModifyDN(PluginOperation modifyDNOperation, 782 Entry oldEntry, Entry newEntry) 783 { 784 if (hasGroupMembershipUpdateControl(modifyDNOperation)) 785 { 786 return; 787 } 788 789 lock.writeLock().lock(); 790 try 791 { 792 Set<Group<?>> groupSet = new HashSet<>(); 793 final DN oldDN = oldEntry.getName(); 794 final DN newDN = newEntry.getName(); 795 groupInstances.removeSubtree(oldDN, groupSet); 796 for (Group<?> group : groupSet) 797 { 798 final DN groupDN = group.getGroupDN(); 799 final DN renamedGroupDN = groupDN.rename(oldDN, newDN); 800 group.setGroupDN(renamedGroupDN); 801 groupInstances.put(renamedGroupDN, group); 802 } 803 if (!groupSet.isEmpty()) 804 { 805 refreshToken++; 806 } 807 } 808 finally 809 { 810 lock.writeLock().unlock(); 811 } 812 } 813 814 @Override 815 public PostOperation doPostOperation( 816 PostOperationAddOperation addOperation) 817 { 818 // Only do something if the operation is successful, meaning there 819 // has been a change. 820 if (addOperation.getResultCode() == ResultCode.SUCCESS) 821 { 822 doPostAdd(addOperation, addOperation.getEntryToAdd()); 823 } 824 825 // If we've gotten here, then everything is acceptable. 826 return PluginResult.PostOperation.continueOperationProcessing(); 827 } 828 829 @Override 830 public PostOperation doPostOperation( 831 PostOperationDeleteOperation deleteOperation) 832 { 833 // Only do something if the operation is successful, meaning there 834 // has been a change. 835 if (deleteOperation.getResultCode() == ResultCode.SUCCESS) 836 { 837 doPostDelete(deleteOperation, deleteOperation.getEntryToDelete()); 838 } 839 840 // If we've gotten here, then everything is acceptable. 841 return PluginResult.PostOperation.continueOperationProcessing(); 842 } 843 844 @Override 845 public PostOperation doPostOperation( 846 PostOperationModifyOperation modifyOperation) 847 { 848 // Only do something if the operation is successful, meaning there 849 // has been a change. 850 if (modifyOperation.getResultCode() == ResultCode.SUCCESS) 851 { 852 doPostModify(modifyOperation, 853 modifyOperation.getCurrentEntry(), 854 modifyOperation.getModifiedEntry(), 855 modifyOperation.getModifications()); 856 } 857 858 // If we've gotten here, then everything is acceptable. 859 return PluginResult.PostOperation.continueOperationProcessing(); 860 } 861 862 @Override 863 public PostOperation doPostOperation( 864 PostOperationModifyDNOperation modifyDNOperation) 865 { 866 // Only do something if the operation is successful, meaning there 867 // has been a change. 868 if (modifyDNOperation.getResultCode() == ResultCode.SUCCESS) 869 { 870 doPostModifyDN(modifyDNOperation, 871 modifyDNOperation.getOriginalEntry(), 872 modifyDNOperation.getUpdatedEntry()); 873 } 874 875 // If we've gotten here, then everything is acceptable. 876 return PluginResult.PostOperation.continueOperationProcessing(); 877 } 878 879 @Override 880 public void doPostSynchronization( 881 PostSynchronizationAddOperation addOperation) 882 { 883 Entry entry = addOperation.getEntryToAdd(); 884 if (entry != null) 885 { 886 doPostAdd(addOperation, entry); 887 } 888 } 889 890 @Override 891 public void doPostSynchronization( 892 PostSynchronizationDeleteOperation deleteOperation) 893 { 894 Entry entry = deleteOperation.getEntryToDelete(); 895 if (entry != null) 896 { 897 doPostDelete(deleteOperation, entry); 898 } 899 } 900 901 @Override 902 public void doPostSynchronization( 903 PostSynchronizationModifyOperation modifyOperation) 904 { 905 Entry entry = modifyOperation.getCurrentEntry(); 906 Entry modEntry = modifyOperation.getModifiedEntry(); 907 if (entry != null && modEntry != null) 908 { 909 doPostModify(modifyOperation, entry, modEntry, modifyOperation.getModifications()); 910 } 911 } 912 913 @Override 914 public void doPostSynchronization( 915 PostSynchronizationModifyDNOperation modifyDNOperation) 916 { 917 Entry oldEntry = modifyDNOperation.getOriginalEntry(); 918 Entry newEntry = modifyDNOperation.getUpdatedEntry(); 919 if (oldEntry != null && newEntry != null) 920 { 921 doPostModifyDN(modifyDNOperation, oldEntry, newEntry); 922 } 923 } 924 925 /** 926 * Attempts to create a group instance from the provided entry, and if that is 927 * successful then register it with the server, overwriting any existing 928 * group instance that may be registered with the same DN. 929 * 930 * @param entry The entry containing the potential group definition. 931 */ 932 private void createAndRegisterGroup(Entry entry) 933 { 934 for (Group<?> groupImplementation : groupImplementations.values()) 935 { 936 try 937 { 938 if (groupImplementation.isGroupDefinition(entry)) 939 { 940 Group<?> groupInstance = groupImplementation.newInstance(serverContext, entry); 941 942 lock.writeLock().lock(); 943 try 944 { 945 groupInstances.put(entry.getName(), groupInstance); 946 refreshToken++; 947 } 948 finally 949 { 950 lock.writeLock().unlock(); 951 } 952 } 953 } 954 catch (DirectoryException e) 955 { 956 logger.traceException(e); 957 } 958 } 959 } 960 961 /** 962 * Removes all group instances that might happen to be registered with the 963 * group manager. This method is only intended for testing purposes and 964 * should not be called by any other code. 965 */ 966 void deregisterAllGroups() 967 { 968 lock.writeLock().lock(); 969 try 970 { 971 groupInstances.clear(); 972 } 973 finally 974 { 975 lock.writeLock().unlock(); 976 } 977 } 978 979 /** 980 * Compare the specified token against the current group manager 981 * token value. Can be used to reload cached group instances if there has 982 * been a group instance change. 983 * 984 * @param token The current token that the group class holds. 985 * 986 * @return {@code true} if the group class should reload its nested groups, 987 * or {@code false} if it shouldn't. 988 */ 989 public boolean hasInstancesChanged(long token) { 990 return token != this.refreshToken; 991 } 992 993 /** 994 * Return the current refresh token value. Can be used to 995 * reload cached group instances if there has been a group instance change. 996 * 997 * @return The current token value. 998 */ 999 public long refreshToken() { 1000 return this.refreshToken; 1001 } 1002}