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