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