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