/* * Copyright (c) OSGi Alliance (2007, 2012). All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.gravia.runtime; import static org.jboss.gravia.runtime.spi.RuntimeLogger.LOGGER; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.jboss.gravia.runtime.Module.State; /** * The {@code ModuleTracker} class simplifies tracking modules much like the * {@code ServiceTracker} simplifies tracking services. * <p> * A {@code ModuleTracker} is constructed with state criteria and a * {@code ModuleTrackerCustomizer} object. A {@code ModuleTracker} can use the * {@code ModuleTrackerCustomizer} to select which modules are tracked and to * create a customized object to be tracked with the module. The * {@code ModuleTracker} can then be opened to begin tracking all modules whose * state matches the specified state criteria. * <p> * The {@code getModules} method can be called to get the {@code Module} objects * of the modules being tracked. The {@code getObject} method can be called to * get the customized object for a tracked module. * <p> * The {@code ModuleTracker} class is thread-safe. It does not call a * {@code ModuleTrackerCustomizer} while holding any locks. * {@code ModuleTrackerCustomizer} implementations must also be thread-safe. * * @param <T> The type of the tracked object. * @ThreadSafe */ public class ModuleTracker<T> implements ModuleTrackerCustomizer<T> { /* set this to true to compile in debug messages */ static final boolean DEBUG = false; /** * The Module Context used by this {@code ModuleTracker}. */ protected final ModuleContext context; /** * The {@code ModuleTrackerCustomizer} object for this tracker. */ final ModuleTrackerCustomizer<T> customizer; /** * Tracked modules: {@code Module} object -> customized Object and * {@code ModuleListener} object */ private volatile Tracked tracked; /** * Accessor method for the current Tracked object. This method is only * intended to be used by the unsynchronized methods which do not modify the * tracked field. * * @return The current Tracked object. */ private Tracked tracked() { return tracked; } /** * State mask for modules being tracked. This field contains the set * of the module states being tracked. */ final Set<State> states = new HashSet<State>(); /** * Create a {@code ModuleTracker} for modules whose state is present in the * specified state mask. * * <p> * Modules whose state is present on the specified state mask will be * tracked by this {@code ModuleTracker}. * * @param context The {@code ModuleContext} against which the tracking is * done. * @param states The list of the module states to be tracked. * @param customizer The customizer object to call when modules are added, * modified, or removed in this {@code ModuleTracker}. If customizer * is {@code null}, then this {@code ModuleTracker} will be used as * the {@code ModuleTrackerCustomizer} and this {@code ModuleTracker} * will call the {@code ModuleTrackerCustomizer} methods on itself. * @see Module#getState() */ public ModuleTracker(ModuleContext context, ModuleTrackerCustomizer<T> customizer, State... states) { this.context = context; this.customizer = (customizer == null) ? this : customizer; if (states != null) { this.states.addAll(Arrays.asList(states)); } } /** * Open this {@code ModuleTracker} and begin tracking modules. * * <p> * Module which match the state criteria specified when this * {@code ModuleTracker} was created are now tracked by this * {@code ModuleTracker}. * * @throws java.lang.IllegalStateException If the {@code ModuleContext} with * which this {@code ModuleTracker} was created is no longer valid. * @throws java.lang.SecurityException If the caller and this class do not * have the appropriate * {@code AdminPermission[context module,LISTENER]}, and the Java * Runtime Environment supports permissions. */ public void open() { final Tracked t; synchronized (this) { if (tracked != null) { return; } if (DEBUG) { LOGGER.debug("ModuleTracker.open"); } t = new Tracked(); synchronized (t) { context.addModuleListener(t); Runtime runtime = RuntimeLocator.getRequiredRuntime(); Set<Module> modules = runtime.getModules(); Iterator<Module> itmods = modules.iterator(); while (itmods.hasNext()) { Module module = itmods.next(); State state = module.getState(); if (!states.contains(state)) { itmods.remove(); } } /* set tracked with the initial modules */ t.setInitial((Module[]) modules.toArray()); } tracked = t; } /* Call tracked outside of synchronized region */ t.trackInitial(); /* process the initial references */ } /** * Close this {@code ModuleTracker}. * * <p> * This method should be called when this {@code ModuleTracker} should end * the tracking of modules. * * <p> * This implementation calls {@link #getModules()} to get the list of * tracked modules to remove. */ public void close() { final Module[] modules; final Tracked outgoing; synchronized (this) { outgoing = tracked; if (outgoing == null) { return; } if (DEBUG) { LOGGER.debug("ModuleTracker.close"); } outgoing.close(); modules = getModules(); tracked = null; try { context.removeModuleListener(outgoing); } catch (IllegalStateException e) { /* In case the context was stopped. */ } } if (modules != null) { for (int i = 0; i < modules.length; i++) { outgoing.untrack(modules[i], null); } } } /** * Default implementation of the * {@code ModuleTrackerCustomizer.addingModule} method. * * <p> * This method is only called when this {@code ModuleTracker} has been * constructed with a {@code null ModuleTrackerCustomizer} argument. * * <p> * This implementation simply returns the specified {@code Module}. * * <p> * This method can be overridden in a subclass to customize the object to be * tracked for the module being added. * * @param module The {@code Module} being added to this * {@code ModuleTracker} object. * @param event The module event which caused this customizer method to be * called or {@code null} if there is no module event associated with * the call to this method. * @return The specified module. * @see ModuleTrackerCustomizer#addingModule(Module, ModuleEvent) */ @SuppressWarnings("unchecked") public T addingModule(Module module, ModuleEvent event) { T result = (T) module; return result; } /** * Default implementation of the * {@code ModuleTrackerCustomizer.modifiedModule} method. * * <p> * This method is only called when this {@code ModuleTracker} has been * constructed with a {@code null ModuleTrackerCustomizer} argument. * * <p> * This implementation does nothing. * * @param module The {@code Module} whose state has been modified. * @param event The module event which caused this customizer method to be * called or {@code null} if there is no module event associated with * the call to this method. * @param object The customized object for the specified Module. * @see ModuleTrackerCustomizer#modifiedModule(Module, ModuleEvent, Object) */ public void modifiedModule(Module module, ModuleEvent event, T object) { /* do nothing */ } /** * Default implementation of the * {@code ModuleTrackerCustomizer.removedModule} method. * * <p> * This method is only called when this {@code ModuleTracker} has been * constructed with a {@code null ModuleTrackerCustomizer} argument. * * <p> * This implementation does nothing. * * @param module The {@code Module} being removed. * @param event The module event which caused this customizer method to be * called or {@code null} if there is no module event associated with * the call to this method. * @param object The customized object for the specified module. * @see ModuleTrackerCustomizer#removedModule(Module, ModuleEvent, Object) */ public void removedModule(Module module, ModuleEvent event, T object) { /* do nothing */ } /** * Return an array of {@code Module}s for all modules being tracked by this * {@code ModuleTracker}. * * @return An array of {@code Module}s or {@code null} if no modules are * being tracked. */ public Module[] getModules() { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return null; } synchronized (t) { int length = t.size(); if (length == 0) { return null; } return t.copyKeys(new Module[length]); } } /** * Returns the customized object for the specified {@code Module} if the * specified module is being tracked by this {@code ModuleTracker}. * * @param module The {@code Module} being tracked. * @return The customized object for the specified {@code Module} or * {@code null} if the specified {@code Module} is not being * tracked. */ public T getObject(Module module) { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return null; } synchronized (t) { return t.getCustomizedObject(module); } } /** * Remove a module from this {@code ModuleTracker}. * * The specified module will be removed from this {@code ModuleTracker} . If * the specified module was being tracked then the * {@code ModuleTrackerCustomizer.removedModule} method will be called for * that module. * * @param module The {@code Module} to be removed. */ public void remove(Module module) { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return; } t.untrack(module, null); } /** * Return the number of modules being tracked by this {@code ModuleTracker}. * * @return The number of modules being tracked. */ public int size() { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return 0; } synchronized (t) { return t.size(); } } /** * Returns the tracking count for this {@code ModuleTracker}. * * The tracking count is initialized to 0 when this {@code ModuleTracker} is * opened. Every time a module is added, modified or removed from this * {@code ModuleTracker} the tracking count is incremented. * * <p> * The tracking count can be used to determine if this {@code ModuleTracker} * has added, modified or removed a module by comparing a tracking count * value previously collected with the current tracking count value. If the * value has not changed, then no module has been added, modified or removed * from this {@code ModuleTracker} since the previous tracking count was * collected. * * @return The tracking count for this {@code ModuleTracker} or -1 if this * {@code ModuleTracker} is not open. */ public int getTrackingCount() { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return -1; } synchronized (t) { return t.getTrackingCount(); } } /** * Return a {@code Map} with the {@code Module}s and customized objects for * all modules being tracked by this {@code ModuleTracker}. * * @return A {@code Map} with the {@code Module}s and customized objects for * all services being tracked by this {@code ModuleTracker}. If no * modules are being tracked, then the returned map is empty. */ public Map<Module, T> getTracked() { Map<Module, T> map = new HashMap<Module, T>(); final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return map; } synchronized (t) { return t.copyEntries(map); } } /** * Return if this {@code ModuleTracker} is empty. * * @return {@code true} if this {@code ModuleTracker} is not tracking any * modules. */ public boolean isEmpty() { final Tracked t = tracked(); if (t == null) { /* if ModuleTracker is not open */ return true; } synchronized (t) { return t.isEmpty(); } } /** * Inner class which subclasses AbstractTracked. This class is the * {@code SynchronousModuleListener} object for the tracker. * * @ThreadSafe */ private final class Tracked extends AbstractTracked<Module, T, ModuleEvent> implements SynchronousModuleListener { /** * Tracked constructor. */ Tracked() { super(); } /** * {@code ModuleListener} method for the {@code ModuleTracker} class. * This method must NOT be synchronized to avoid deadlock potential. * * @param event {@code ModuleEvent} object from the framework. */ public void moduleChanged(final ModuleEvent event) { /* * Check if we had a delayed call (which could happen when we * close). */ if (closed) { return; } final Module module = event.getModule(); final State state = module.getState(); if (DEBUG) { LOGGER.debug("ModuleTracker.Tracked.moduleChanged[" + state + "]: " + module); } if (states.contains(state)) { track(module, event); /* * If the customizer throws an unchecked exception, it is safe * to let it propagate */ } else { untrack(module, event); /* * If the customizer throws an unchecked exception, it is safe * to let it propagate */ } } /** * Call the specific customizer adding method. This method must not be * called while synchronized on this object. * * @param item Item to be tracked. * @param related Action related object. * @return Customized object for the tracked item or {@code null} if the * item is not to be tracked. */ T customizerAdding(final Module item, final ModuleEvent related) { return customizer.addingModule(item, related); } /** * Call the specific customizer modified method. This method must not be * called while synchronized on this object. * * @param item Tracked item. * @param related Action related object. * @param object Customized object for the tracked item. */ void customizerModified(final Module item, final ModuleEvent related, final T object) { customizer.modifiedModule(item, related, object); } /** * Call the specific customizer removed method. This method must not be * called while synchronized on this object. * * @param item Tracked item. * @param related Action related object. * @param object Customized object for the tracked item. */ void customizerRemoved(final Module item, final ModuleEvent related, final T object) { customizer.removedModule(item, related, object); } } }