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 performBackendInitializationProcessing(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 performBackendFinalizationProcessing(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 697 698 /** 699 * In this case, each entry is checked to see if it contains 700 * a group definition, and if so it will be instantiated and 701 * registered with this group manager. 702 */ 703 private void doPostAdd(PluginOperation addOperation, Entry entry) 704 { 705 if (hasGroupMembershipUpdateControl(addOperation)) 706 { 707 return; 708 } 709 710 createAndRegisterGroup(entry); 711 } 712 713 714 715 private static boolean hasGroupMembershipUpdateControl(PluginOperation operation) 716 { 717 List<Control> requestControls = operation.getRequestControls(); 718 if (requestControls != null) 719 { 720 for (Control c : requestControls) 721 { 722 if (OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE.equals(c.getOID())) 723 { 724 return true; 725 } 726 } 727 } 728 return false; 729 } 730 731 732 733 /** 734 * In this case, if the entry is associated with a registered 735 * group instance, then that group instance will be deregistered. 736 */ 737 private void doPostDelete(PluginOperation deleteOperation, Entry entry) 738 { 739 if (hasGroupMembershipUpdateControl(deleteOperation)) 740 { 741 return; 742 } 743 744 lock.writeLock().lock(); 745 try 746 { 747 if (groupInstances.removeSubtree(entry.getName(), null)) 748 { 749 refreshToken++; 750 } 751 } 752 finally 753 { 754 lock.writeLock().unlock(); 755 } 756 } 757 758 759 760 /** 761 * In this case, if the entry is associated with a registered 762 * group instance, then that instance will be recreated from 763 * the contents of the provided entry and re-registered with 764 * the group manager. 765 */ 766 private void doPostModify(PluginOperation modifyOperation, 767 Entry oldEntry, Entry newEntry) 768 { 769 if (hasGroupMembershipUpdateControl(modifyOperation)) 770 { 771 return; 772 } 773 774 lock.readLock().lock(); 775 try 776 { 777 if (!groupInstances.containsKey(oldEntry.getName())) 778 { 779 // If the modified entry is not in any group instance, it's probably 780 // not a group, exit fast 781 return; 782 } 783 } 784 finally 785 { 786 lock.readLock().unlock(); 787 } 788 789 lock.writeLock().lock(); 790 try 791 { 792 if (groupInstances.containsKey(oldEntry.getName())) 793 { 794 if (! oldEntry.getName().equals(newEntry.getName())) 795 { 796 // This should never happen, but check for it anyway. 797 groupInstances.remove(oldEntry.getName()); 798 } 799 createAndRegisterGroup(newEntry); 800 } 801 } 802 finally 803 { 804 lock.writeLock().unlock(); 805 } 806 } 807 808 809 810 /** 811 * In this case, if the entry is associated with a registered 812 * group instance, then that instance will be recreated from 813 * the contents of the provided entry and re-registered with 814 * the group manager under the new DN, and the old instance 815 * will be deregistered. 816 */ 817 private void doPostModifyDN(PluginOperation modifyDNOperation, 818 Entry oldEntry, Entry newEntry) 819 { 820 if (hasGroupMembershipUpdateControl(modifyDNOperation)) 821 { 822 return; 823 } 824 825 lock.writeLock().lock(); 826 try 827 { 828 Set<Group<?>> groupSet = new HashSet<>(); 829 final DN oldDN = oldEntry.getName(); 830 final DN newDN = newEntry.getName(); 831 groupInstances.removeSubtree(oldDN, groupSet); 832 for (Group<?> group : groupSet) 833 { 834 final DN groupDN = group.getGroupDN(); 835 final DN renamedGroupDN = groupDN.rename(oldDN, newDN); 836 group.setGroupDN(renamedGroupDN); 837 groupInstances.put(renamedGroupDN, group); 838 } 839 if (!groupSet.isEmpty()) 840 { 841 refreshToken++; 842 } 843 } 844 finally 845 { 846 lock.writeLock().unlock(); 847 } 848 } 849 850 851 852 /** {@inheritDoc} */ 853 @Override 854 public PostOperation doPostOperation( 855 PostOperationAddOperation addOperation) 856 { 857 // Only do something if the operation is successful, meaning there 858 // has been a change. 859 if (addOperation.getResultCode() == ResultCode.SUCCESS) 860 { 861 doPostAdd(addOperation, addOperation.getEntryToAdd()); 862 } 863 864 // If we've gotten here, then everything is acceptable. 865 return PluginResult.PostOperation.continueOperationProcessing(); 866 } 867 868 /** {@inheritDoc} */ 869 @Override 870 public PostOperation doPostOperation( 871 PostOperationDeleteOperation deleteOperation) 872 { 873 // Only do something if the operation is successful, meaning there 874 // has been a change. 875 if (deleteOperation.getResultCode() == ResultCode.SUCCESS) 876 { 877 doPostDelete(deleteOperation, deleteOperation.getEntryToDelete()); 878 } 879 880 // If we've gotten here, then everything is acceptable. 881 return PluginResult.PostOperation.continueOperationProcessing(); 882 } 883 884 /** {@inheritDoc} */ 885 @Override 886 public PostOperation doPostOperation( 887 PostOperationModifyOperation modifyOperation) 888 { 889 // Only do something if the operation is successful, meaning there 890 // has been a change. 891 if (modifyOperation.getResultCode() == ResultCode.SUCCESS) 892 { 893 doPostModify(modifyOperation, 894 modifyOperation.getCurrentEntry(), 895 modifyOperation.getModifiedEntry()); 896 } 897 898 // If we've gotten here, then everything is acceptable. 899 return PluginResult.PostOperation.continueOperationProcessing(); 900 } 901 902 /** {@inheritDoc} */ 903 @Override 904 public PostOperation doPostOperation( 905 PostOperationModifyDNOperation modifyDNOperation) 906 { 907 // Only do something if the operation is successful, meaning there 908 // has been a change. 909 if (modifyDNOperation.getResultCode() == ResultCode.SUCCESS) 910 { 911 doPostModifyDN(modifyDNOperation, 912 modifyDNOperation.getOriginalEntry(), 913 modifyDNOperation.getUpdatedEntry()); 914 } 915 916 // If we've gotten here, then everything is acceptable. 917 return PluginResult.PostOperation.continueOperationProcessing(); 918 } 919 920 /** {@inheritDoc} */ 921 @Override 922 public void doPostSynchronization( 923 PostSynchronizationAddOperation addOperation) 924 { 925 Entry entry = addOperation.getEntryToAdd(); 926 if (entry != null) 927 { 928 doPostAdd(addOperation, entry); 929 } 930 } 931 932 /** {@inheritDoc} */ 933 @Override 934 public void doPostSynchronization( 935 PostSynchronizationDeleteOperation deleteOperation) 936 { 937 Entry entry = deleteOperation.getEntryToDelete(); 938 if (entry != null) 939 { 940 doPostDelete(deleteOperation, entry); 941 } 942 } 943 944 /** {@inheritDoc} */ 945 @Override 946 public void doPostSynchronization( 947 PostSynchronizationModifyOperation modifyOperation) 948 { 949 Entry entry = modifyOperation.getCurrentEntry(); 950 Entry modEntry = modifyOperation.getModifiedEntry(); 951 if (entry != null && modEntry != null) 952 { 953 doPostModify(modifyOperation, entry, modEntry); 954 } 955 } 956 957 /** {@inheritDoc} */ 958 @Override 959 public void doPostSynchronization( 960 PostSynchronizationModifyDNOperation modifyDNOperation) 961 { 962 Entry oldEntry = modifyDNOperation.getOriginalEntry(); 963 Entry newEntry = modifyDNOperation.getUpdatedEntry(); 964 if (oldEntry != null && newEntry != null) 965 { 966 doPostModifyDN(modifyDNOperation, oldEntry, newEntry); 967 } 968 } 969 970 971 972 /** 973 * Attempts to create a group instance from the provided entry, and if that is 974 * successful then register it with the server, overwriting any existing 975 * group instance that may be registered with the same DN. 976 * 977 * @param entry The entry containing the potential group definition. 978 */ 979 private void createAndRegisterGroup(Entry entry) 980 { 981 for (Group<?> groupImplementation : groupImplementations.values()) 982 { 983 try 984 { 985 if (groupImplementation.isGroupDefinition(entry)) 986 { 987 Group<?> groupInstance = groupImplementation.newInstance(null, entry); 988 989 lock.writeLock().lock(); 990 try 991 { 992 groupInstances.put(entry.getName(), groupInstance); 993 refreshToken++; 994 } 995 finally 996 { 997 lock.writeLock().unlock(); 998 } 999 } 1000 } 1001 catch (DirectoryException e) 1002 { 1003 logger.traceException(e); 1004 } 1005 } 1006 } 1007 1008 1009 1010 /** 1011 * Removes all group instances that might happen to be registered with the 1012 * group manager. This method is only intended for testing purposes and 1013 * should not be called by any other code. 1014 */ 1015 void deregisterAllGroups() 1016 { 1017 lock.writeLock().lock(); 1018 try 1019 { 1020 groupInstances.clear(); 1021 } 1022 finally 1023 { 1024 lock.writeLock().unlock(); 1025 } 1026 } 1027 1028 1029 /** 1030 * Compare the specified token against the current group manager 1031 * token value. Can be used to reload cached group instances if there has 1032 * been a group instance change. 1033 * 1034 * @param token The current token that the group class holds. 1035 * 1036 * @return {@code true} if the group class should reload its nested groups, 1037 * or {@code false} if it shouldn't. 1038 */ 1039 public boolean hasInstancesChanged(long token) { 1040 return token != this.refreshToken; 1041 } 1042 1043 /** 1044 * Return the current refresh token value. Can be used to 1045 * reload cached group instances if there has been a group instance change. 1046 * 1047 * @return The current token value. 1048 */ 1049 public long refreshToken() { 1050 return this.refreshToken; 1051 } 1052} 1053