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