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
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2007-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.core;
028    
029    
030    
031    import java.lang.reflect.Method;
032    import java.util.*;
033    import java.util.concurrent.ConcurrentHashMap;
034    
035    import org.opends.messages.Message;
036    import org.opends.server.admin.ClassPropertyDefinition;
037    import org.opends.server.admin.server.ConfigurationAddListener;
038    import org.opends.server.admin.server.ConfigurationChangeListener;
039    import org.opends.server.admin.server.ConfigurationDeleteListener;
040    import org.opends.server.admin.server.ServerManagementContext;
041    import org.opends.server.admin.std.meta.GroupImplementationCfgDefn;
042    import org.opends.server.admin.std.server.GroupImplementationCfg;
043    import org.opends.server.admin.std.server.RootCfg;
044    import org.opends.server.api.Backend;
045    import org.opends.server.api.BackendInitializationListener;
046    import org.opends.server.api.ChangeNotificationListener;
047    import org.opends.server.api.Group;
048    import org.opends.server.config.ConfigException;
049    import org.opends.server.loggers.debug.DebugTracer;
050    import org.opends.server.protocols.internal.InternalClientConnection;
051    import org.opends.server.protocols.internal.InternalSearchOperation;
052    import org.opends.server.types.ConfigChangeResult;
053    import org.opends.server.types.Control;
054    import org.opends.server.types.DebugLogLevel;
055    import org.opends.server.types.DereferencePolicy;
056    import org.opends.server.types.DN;
057    import org.opends.server.types.Entry;
058    import org.opends.server.types.InitializationException;
059    import org.opends.server.types.ResultCode;
060    import org.opends.server.types.SearchResultEntry;
061    import org.opends.server.types.SearchScope;
062    import org.opends.server.types.SearchFilter;
063    import org.opends.server.types.operation.PostResponseAddOperation;
064    import org.opends.server.types.operation.PostResponseDeleteOperation;
065    import org.opends.server.types.operation.PostResponseModifyOperation;
066    import org.opends.server.types.operation.PostResponseModifyDNOperation;
067    import org.opends.server.workflowelement.localbackend.
068                LocalBackendSearchOperation;
069    
070    import static org.opends.messages.ConfigMessages.*;
071    import static org.opends.messages.CoreMessages.*;
072    import static org.opends.server.loggers.debug.DebugLogger.*;
073    import static org.opends.server.loggers.ErrorLogger.*;
074    import static org.opends.server.util.ServerConstants.*;
075    import static org.opends.server.util.StaticUtils.*;
076    
077    
078    
079    /**
080     * This class provides a mechanism for interacting with all groups defined in
081     * the Directory Server.  It will handle all necessary processing at server
082     * startup to identify and load all group implementations, as well as to find
083     * all group instances within the server.
084     * <BR><BR>
085     * FIXME:  At the present time, it assumes that all of the necessary
086     * information about all of the groups defined in the server can be held in
087     * memory.  If it is determined that this approach is not workable in all cases,
088     * then we will need an alternate strategy.
089     */
090    public class GroupManager
091           implements ConfigurationChangeListener<GroupImplementationCfg>,
092                      ConfigurationAddListener<GroupImplementationCfg>,
093                      ConfigurationDeleteListener<GroupImplementationCfg>,
094                      BackendInitializationListener,
095                      ChangeNotificationListener
096    {
097      /**
098       * The tracer object for the debug logger.
099       */
100      private static final DebugTracer TRACER = getTracer();
101    
102    
103      //Used by group instances to determine if new groups have been
104      //registered or groups deleted.
105      private long refreshToken=0;
106    
107    
108      // A mapping between the DNs of the config entries and the associated
109      // group implementations.
110      private ConcurrentHashMap<DN,Group> groupImplementations;
111    
112      // A mapping between the DNs of all group entries and the corresponding
113      // group instances.
114      private ConcurrentHashMap<DN,Group> groupInstances;
115    
116    
117    
118      /**
119       * Creates a new instance of this group manager.
120       */
121      public GroupManager()
122      {
123        groupImplementations = new ConcurrentHashMap<DN,Group>();
124        groupInstances       = new ConcurrentHashMap<DN,Group>();
125    
126        DirectoryServer.registerBackendInitializationListener(this);
127        DirectoryServer.registerChangeNotificationListener(this);
128      }
129    
130    
131    
132      /**
133       * Initializes all group implementations currently defined in the Directory
134       * Server configuration.  This should only be called at Directory Server
135       * startup.
136       *
137       * @throws  ConfigException  If a configuration problem causes the group
138       *                           implementation initialization process to fail.
139       *
140       * @throws  InitializationException  If a problem occurs while initializing
141       *                                   the group implementations that is not
142       *                                   related to the server configuration.
143       */
144      public void initializeGroupImplementations()
145             throws ConfigException, InitializationException
146      {
147        // Get the root configuration object.
148        ServerManagementContext managementContext =
149             ServerManagementContext.getInstance();
150        RootCfg rootConfiguration =
151             managementContext.getRootConfiguration();
152    
153    
154        // Register as an add and delete listener with the root configuration so we
155        // can be notified if any group implementation entries are added or removed.
156        rootConfiguration.addGroupImplementationAddListener(this);
157        rootConfiguration.addGroupImplementationDeleteListener(this);
158    
159    
160        //Initialize the existing group implementations.
161        for (String name : rootConfiguration.listGroupImplementations())
162        {
163          GroupImplementationCfg groupConfiguration =
164               rootConfiguration.getGroupImplementation(name);
165          groupConfiguration.addChangeListener(this);
166    
167          if (groupConfiguration.isEnabled())
168          {
169            String className = groupConfiguration.getJavaClass();
170            try
171            {
172              Group group = loadGroup(className, groupConfiguration, true);
173              groupImplementations.put(groupConfiguration.dn(), group);
174            }
175            catch (InitializationException ie)
176            {
177              logError(ie.getMessageObject());
178              continue;
179            }
180          }
181        }
182      }
183    
184    
185    
186      /**
187       * {@inheritDoc}
188       */
189      public boolean isConfigurationAddAcceptable(
190                          GroupImplementationCfg configuration,
191                          List<Message> unacceptableReasons)
192      {
193        if (configuration.isEnabled())
194        {
195          // Get the name of the class and make sure we can instantiate it as a
196          // group implementation.
197          String className = configuration.getJavaClass();
198          try
199          {
200            loadGroup(className, configuration, false);
201          }
202          catch (InitializationException ie)
203          {
204            unacceptableReasons.add(ie.getMessageObject());
205            return false;
206          }
207        }
208    
209        // If we've gotten here, then it's fine.
210        return true;
211      }
212    
213    
214    
215      /**
216       * {@inheritDoc}
217       */
218      public ConfigChangeResult applyConfigurationAdd(
219                                     GroupImplementationCfg configuration)
220      {
221        ResultCode        resultCode          = ResultCode.SUCCESS;
222        boolean           adminActionRequired = false;
223        ArrayList<Message> messages            = new ArrayList<Message>();
224    
225        configuration.addChangeListener(this);
226    
227        if (! configuration.isEnabled())
228        {
229          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
230        }
231    
232        Group group = null;
233    
234        // Get the name of the class and make sure we can instantiate it as a group
235        // implementation.
236        String className = configuration.getJavaClass();
237        try
238        {
239          group = loadGroup(className, configuration, true);
240        }
241        catch (InitializationException ie)
242        {
243          if (resultCode == ResultCode.SUCCESS)
244          {
245            resultCode = DirectoryServer.getServerErrorResultCode();
246          }
247    
248          messages.add(ie.getMessageObject());
249        }
250    
251        if (resultCode == ResultCode.SUCCESS)
252        {
253          groupImplementations.put(configuration.dn(), group);
254        }
255    
256        // FIXME -- We need to make sure to find all groups of this type in the
257        // server before returning.
258    
259        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
260      }
261    
262    
263    
264      /**
265       * {@inheritDoc}
266       */
267      public boolean isConfigurationDeleteAcceptable(
268                          GroupImplementationCfg configuration,
269                          List<Message> unacceptableReasons)
270      {
271        // FIXME -- We should try to perform some check to determine whether the
272        // group implementation is in use.
273        return true;
274      }
275    
276    
277    
278      /**
279       * {@inheritDoc}
280       */
281      public ConfigChangeResult applyConfigurationDelete(
282                                     GroupImplementationCfg configuration)
283      {
284        ResultCode        resultCode          = ResultCode.SUCCESS;
285        boolean           adminActionRequired = false;
286        ArrayList<Message> messages            = new ArrayList<Message>();
287    
288        Group group = groupImplementations.remove(configuration.dn());
289        if (group != null)
290        {
291          Iterator<Group> iterator = groupInstances.values().iterator();
292          while (iterator.hasNext())
293          {
294            Group g = iterator.next();
295            if (g.getClass().getName().equals(group.getClass().getName()))
296            {
297              iterator.remove();
298            }
299          }
300    
301          group.finalizeGroupImplementation();
302        }
303    
304        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
305      }
306    
307    
308    
309      /**
310       * {@inheritDoc}
311       */
312      public boolean isConfigurationChangeAcceptable(
313                          GroupImplementationCfg configuration,
314                          List<Message> unacceptableReasons)
315      {
316        if (configuration.isEnabled())
317        {
318          // Get the name of the class and make sure we can instantiate it as a
319          // group implementation.
320          String className = configuration.getJavaClass();
321          try
322          {
323            loadGroup(className, configuration, false);
324          }
325          catch (InitializationException ie)
326          {
327            unacceptableReasons.add(ie.getMessageObject());
328            return false;
329          }
330        }
331    
332        // If we've gotten here, then it's fine.
333        return true;
334      }
335    
336    
337    
338      /**
339       * {@inheritDoc}
340       */
341      public ConfigChangeResult applyConfigurationChange(
342                                     GroupImplementationCfg configuration)
343      {
344        ResultCode        resultCode          = ResultCode.SUCCESS;
345        boolean           adminActionRequired = false;
346        ArrayList<Message> messages            = new ArrayList<Message>();
347    
348    
349        // Get the existing group implementation if it's already enabled.
350        Group existingGroup = groupImplementations.get(configuration.dn());
351    
352    
353        // If the new configuration has the group implementation disabled, then
354        // disable it if it is enabled, or do nothing if it's already disabled.
355        if (! configuration.isEnabled())
356        {
357          if (existingGroup != null)
358          {
359            Group group = groupImplementations.remove(configuration.dn());
360            if (group != null)
361            {
362              Iterator<Group> iterator = groupInstances.values().iterator();
363              while (iterator.hasNext())
364              {
365                Group g = iterator.next();
366                if (g.getClass().getName().equals(group.getClass().getName()))
367                {
368                  iterator.remove();
369                }
370              }
371    
372              group.finalizeGroupImplementation();
373            }
374          }
375    
376          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
377        }
378    
379    
380        // Get the class for the group implementation.  If the group is already
381        // enabled, then we shouldn't do anything with it although if the class has
382        // changed then we'll at least need to indicate that administrative action
383        // is required.  If the group implementation is disabled, then instantiate
384        // the class and initialize and register it as a group implementation.
385        String className = configuration.getJavaClass();
386        if (existingGroup != null)
387        {
388          if (! className.equals(existingGroup.getClass().getName()))
389          {
390            adminActionRequired = true;
391          }
392    
393          return new ConfigChangeResult(resultCode, adminActionRequired, messages);
394        }
395    
396        Group group = null;
397        try
398        {
399          group = loadGroup(className, configuration, true);
400        }
401        catch (InitializationException ie)
402        {
403          if (resultCode == ResultCode.SUCCESS)
404          {
405            resultCode = DirectoryServer.getServerErrorResultCode();
406          }
407    
408          messages.add(ie.getMessageObject());
409        }
410    
411        if (resultCode == ResultCode.SUCCESS)
412        {
413          groupImplementations.put(configuration.dn(), group);
414        }
415    
416        // FIXME -- We need to make sure to find all groups of this type in the
417        // server before returning.
418    
419        return new ConfigChangeResult(resultCode, adminActionRequired, messages);
420      }
421    
422    
423    
424      /**
425       * Loads the specified class, instantiates it as a group implementation, and
426       * optionally initializes that instance.
427       *
428       * @param  className      The fully-qualified name of the group implementation
429       *                        class to load, instantiate, and initialize.
430       * @param  configuration  The configuration to use to initialize the group
431       *                        implementation.  It must not be {@code null}.
432       * @param  initialize     Indicates whether the group implementation instance
433       *                        should be initialized.
434       *
435       * @return  The possibly initialized group implementation.
436       *
437       * @throws  InitializationException  If a problem occurred while attempting to
438       *                                   initialize the group implementation.
439       */
440      private Group loadGroup(String className,
441                              GroupImplementationCfg configuration,
442                              boolean initialize)
443              throws InitializationException
444      {
445        try
446        {
447          GroupImplementationCfgDefn definition =
448               GroupImplementationCfgDefn.getInstance();
449          ClassPropertyDefinition propertyDefinition =
450               definition.getJavaClassPropertyDefinition();
451          Class<? extends Group> groupClass =
452               propertyDefinition.loadClass(className, Group.class);
453          Group group = groupClass.newInstance();
454    
455          if (initialize)
456          {
457            Method method = group.getClass()
458                .getMethod("initializeGroupImplementation",
459                    configuration.configurationClass());
460            method.invoke(group, configuration);
461          }
462          else
463          {
464            Method method = group.getClass().getMethod("isConfigurationAcceptable",
465                                                       GroupImplementationCfg.class,
466                                                       List.class);
467    
468            List<Message> unacceptableReasons = new ArrayList<Message>();
469            Boolean acceptable = (Boolean) method.invoke(group, configuration,
470                                                         unacceptableReasons);
471            if (! acceptable)
472            {
473              StringBuilder buffer = new StringBuilder();
474              if (! unacceptableReasons.isEmpty())
475              {
476                Iterator<Message> iterator = unacceptableReasons.iterator();
477                buffer.append(iterator.next());
478                while (iterator.hasNext())
479                {
480                  buffer.append(".  ");
481                  buffer.append(iterator.next());
482                }
483              }
484    
485              Message message = ERR_CONFIG_GROUP_CONFIG_NOT_ACCEPTABLE.get(
486                  String.valueOf(configuration.dn()), buffer.toString());
487              throw new InitializationException(message);
488            }
489          }
490    
491          return group;
492        }
493        catch (Exception e)
494        {
495          Message message = ERR_CONFIG_GROUP_INITIALIZATION_FAILED.
496              get(className, String.valueOf(configuration.dn()),
497                  stackTraceToSingleLineString(e));
498          throw new InitializationException(message, e);
499        }
500      }
501    
502    
503    
504      /**
505       * Performs any cleanup work that may be needed when the server is shutting
506       * down.
507       */
508      public void finalizeGroupManager()
509      {
510        deregisterAllGroups();
511    
512        for (Group groupImplementation : groupImplementations.values())
513        {
514          groupImplementation.finalizeGroupImplementation();
515        }
516    
517        groupImplementations.clear();
518      }
519    
520    
521    
522      /**
523       * Retrieves an {@code Iterable} object that may be used to cursor across the
524       * group implementations defined in the server.
525       *
526       * @return  An {@code Iterable} object that may be used to cursor across the
527       *          group implementations defined in the server.
528       */
529      public Iterable<Group> getGroupImplementations()
530      {
531        return groupImplementations.values();
532      }
533    
534    
535    
536      /**
537       * Retrieves an {@code Iterable} object that may be used to cursor across the
538       * group instances defined in the server.
539       *
540       * @return  An {@code Iterable} object that may be used to cursor across the
541       *          group instances defined in the server.
542       */
543      public Iterable<Group> getGroupInstances()
544      {
545        return groupInstances.values();
546      }
547    
548    
549    
550      /**
551       * Retrieves the group instance defined in the entry with the specified DN.
552       *
553       * @param  entryDN  The DN of the entry containing the definition of the group
554       *                  instance to retrieve.
555       *
556       * @return  The group instance defined in the entry with the specified DN, or
557       *          {@code null} if no such group is currently defined.
558       */
559      public Group getGroupInstance(DN entryDN)
560      {
561        Group group = groupInstances.get(entryDN);
562        if (group == null)
563        {
564          // FIXME -- Should we try to retrieve the corresponding entry and see if
565          // it is a group?
566        }
567    
568        return group;
569      }
570    
571    
572    
573      /**
574       * {@inheritDoc}  In this case, the server will search the backend to find
575       * all group instances that it may contain and register them with this group
576       * manager.
577       */
578      public void performBackendInitializationProcessing(Backend backend)
579      {
580        InternalClientConnection conn =
581             InternalClientConnection.getRootConnection();
582    
583        LinkedList<Control> requestControls = new LinkedList<Control>();
584        requestControls.add(new Control(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE,
585                                        false));
586        for (DN configEntryDN : groupImplementations.keySet())
587        {
588          SearchFilter filter;
589          Group groupImplementation = groupImplementations.get(configEntryDN);
590          try
591          {
592            filter = groupImplementation.getGroupDefinitionFilter();
593            if (! backend.isIndexed(filter))
594            {
595              logError(WARN_GROUP_FILTER_NOT_INDEXED.get(String.valueOf(filter),
596                            String.valueOf(configEntryDN), backend.getBackendID()));
597            }
598          }
599          catch (Exception e)
600          {
601            if (debugEnabled())
602            {
603              TRACER.debugCaught(DebugLogLevel.ERROR, e);
604            }
605    
606            // FIXME -- Is there anything that we need to do here?
607            continue;
608          }
609    
610    
611          for (DN baseDN : backend.getBaseDNs())
612          {
613            try
614            {
615              if (! backend.entryExists(baseDN))
616              {
617                continue;
618              }
619            }
620            catch (Exception e)
621            {
622              if (debugEnabled())
623              {
624                TRACER.debugCaught(DebugLogLevel.ERROR, e);
625              }
626    
627              // FIXME -- Is there anything that we need to do here?
628              continue;
629            }
630    
631    
632            InternalSearchOperation internalSearch =
633                 new InternalSearchOperation(conn, conn.nextOperationID(),
634                                             conn.nextMessageID(), requestControls,
635                                             baseDN,
636                                             SearchScope.WHOLE_SUBTREE,
637                                             DereferencePolicy.NEVER_DEREF_ALIASES,
638                                             0, 0, false, filter, null, null);
639            LocalBackendSearchOperation localSearch =
640              new LocalBackendSearchOperation(internalSearch);
641            try
642            {
643              backend.search(localSearch);
644            }
645            catch (Exception e)
646            {
647              if (debugEnabled())
648              {
649                TRACER.debugCaught(DebugLogLevel.ERROR, e);
650              }
651    
652              // FIXME -- Is there anything that we need to do here?
653              continue;
654            }
655    
656            for (SearchResultEntry entry : internalSearch.getSearchEntries())
657            {
658              try
659              {
660                Group groupInstance = groupImplementation.newInstance(entry);
661                groupInstances.put(entry.getDN(), groupInstance);
662                refreshToken++;
663              }
664              catch (Exception e)
665              {
666                if (debugEnabled())
667                {
668                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
669                }
670    
671                // FIXME -- Handle this.
672                continue;
673              }
674            }
675          }
676        }
677      }
678    
679    
680    
681      /**
682       * {@inheritDoc}  In this case, the server will de-register all group
683       * instances associated with entries in the provided backend.
684       */
685      public void performBackendFinalizationProcessing(Backend backend)
686      {
687        Iterator<Map.Entry<DN,Group>> iterator =
688             groupInstances.entrySet().iterator();
689        while (iterator.hasNext())
690        {
691          Map.Entry<DN,Group> mapEntry = iterator.next();
692          DN groupEntryDN = mapEntry.getKey();
693          if (backend.handlesEntry(groupEntryDN))
694          {
695            iterator.remove();
696          }
697        }
698      }
699    
700    
701    
702      /**
703       * {@inheritDoc}  In this case, each entry is checked to see if it contains
704       * a group definition, and if so it will be instantiated and registered with
705       * this group manager.
706       */
707      public void handleAddOperation(PostResponseAddOperation addOperation,
708                                     Entry entry)
709      {
710        List<Control> requestControls = addOperation.getRequestControls();
711        if (requestControls != null)
712        {
713          for (Control c : requestControls)
714          {
715            if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE))
716            {
717              return;
718            }
719          }
720        }
721        synchronized (groupInstances)
722        {
723          createAndRegisterGroup(entry);
724          refreshToken++;
725        }
726      }
727    
728    
729    
730      /**
731       * {@inheritDoc}  In this case, if the entry is associated with a registered
732       * group instance, then that group instance will be deregistered.
733       */
734      public void handleDeleteOperation(PostResponseDeleteOperation deleteOperation,
735                                        Entry entry)
736      {
737        List<Control> requestControls = deleteOperation.getRequestControls();
738        if (requestControls != null)
739        {
740          for (Control c : requestControls)
741          {
742            if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE))
743            {
744              return;
745            }
746          }
747        }
748        synchronized (groupInstances)
749        {
750          groupInstances.remove(entry.getDN());
751          refreshToken++;
752        }
753      }
754    
755    
756    
757      /**
758       * {@inheritDoc}  In this case, if the entry is associated with a registered
759       * group instance, then that instance will be recreated from the contents of
760       * the provided entry and re-registered with the group manager.
761       */
762      public void handleModifyOperation(PostResponseModifyOperation modifyOperation,
763                                        Entry oldEntry, Entry newEntry)
764      {
765        List<Control> requestControls = modifyOperation.getRequestControls();
766        if (requestControls != null)
767        {
768          for (Control c : requestControls)
769          {
770            if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE))
771            {
772              return;
773            }
774          }
775        }
776    
777    
778        if (groupInstances.containsKey(oldEntry.getDN()))
779        {
780          synchronized (groupInstances)
781          {
782            if (! oldEntry.getDN().equals(newEntry.getDN()))
783            {
784              // This should never happen, but check for it anyway.
785              groupInstances.remove(oldEntry.getDN());
786            }
787    
788            createAndRegisterGroup(newEntry);
789            refreshToken++;
790          }
791        }
792      }
793    
794    
795    
796      /**
797       * {@inheritDoc}  In this case, if the entry is associated with a registered
798       * group instance, then that instance will be recreated from the contents of
799       * the provided entry and re-registered with the group manager under the new
800       * DN, and the old instance will be deregistered.
801       */
802      public void handleModifyDNOperation(
803                       PostResponseModifyDNOperation modifyDNOperation,
804                       Entry oldEntry, Entry newEntry)
805      {
806        List<Control> requestControls = modifyDNOperation.getRequestControls();
807        if (requestControls != null)
808        {
809          for (Control c : requestControls)
810          {
811            if (c.getOID().equals(OID_INTERNAL_GROUP_MEMBERSHIP_UPDATE))
812            {
813              return;
814            }
815          }
816        }
817    
818        if (groupInstances.containsKey(oldEntry.getDN()))
819        {
820          synchronized (groupInstances)
821          {
822            createAndRegisterGroup(newEntry);
823            groupInstances.remove(oldEntry.getDN());
824            refreshToken++;
825          }
826        }
827      }
828    
829    
830    
831      /**
832       * Attempts to create a group instance from the provided entry, and if that is
833       * successful then register it with the server, overwriting any existing
834       * group instance that may be registered with the same DN.
835       *
836       * @param  entry  The entry containing the potential group definition.
837       */
838      private void createAndRegisterGroup(Entry entry)
839      {
840        for (Group groupImplementation : groupImplementations.values())
841        {
842          try
843          {
844            if (groupImplementation.isGroupDefinition(entry))
845            {
846              Group groupInstance = groupImplementation.newInstance(entry);
847              groupInstances.put(entry.getDN(), groupInstance);
848            }
849          }
850          catch (Exception e)
851          {
852            if (debugEnabled())
853            {
854              TRACER.debugCaught(DebugLogLevel.ERROR, e);
855            }
856    
857            // FIXME -- Do we need to do anything else?
858          }
859        }
860      }
861    
862    
863    
864      /**
865       * Removes all group instances that might happen to be registered with the
866       * group manager.  This method is only intended for testing purposes and
867       * should not be called by any other code.
868       */
869      void deregisterAllGroups()
870      {
871        groupInstances.clear();
872      }
873    
874    
875      /**
876       * Compare the specified token against the current group manager
877       * token value. Can be used to reload cached group instances if there has
878       * been a group instance change.
879       *
880       * @param token The current token that the group class holds.
881       *
882       * @return {@code true} if the group class should reload its nested groups,
883       *         or {@code false} if it shouldn't.
884       */
885      public boolean hasInstancesChanged(long token)  {
886        return token != this.refreshToken;
887      }
888    
889      /**
890       * Return the current refresh token value. Can be used to
891       * reload cached group instances if there has been a group instance change.
892       *
893       * @return The current token value.
894       */
895      public long refreshToken() {
896        return this.refreshToken;
897      }
898    }
899