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