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