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