/*
* The Sun Project JXTA(TM) Software License
*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.content;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import net.jxta.document.Advertisement;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.ID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.Module;
import static net.jxta.impl.content.ModuleLifecycleState.*;
/**
* Manages the lifecycle of a set of subordinate Modules such that a goal
* state can be set and the subordinate Modules will make their
* way toward the goal state using the semantics defined by the return
* value of <code>Module.startApp()</code>. Although the API presented by
* this class has the ability to manage Modules spanning more than one
* PeerGroup, the intent is for a manager instance to be created per peer
* group, allowing the entire set of Modules in the PeerGroup to be centrally
* managed by a single control point.
*
* @param <T> Module type to be managed
*/
public class ModuleLifecycleManager<T extends Module> {
/**
* The maximum number of consecutive times a tracker state check can
* stall before terminal failure.
*/
private static final int MAX_STALLS =
Integer.getInteger(ModuleLifecycleManager.class.getName()
+ ".maxStalls", 10);
/**
* Current state of the manager. This acts as the target state for all
* subordinate Module.
*/
private ModuleLifecycleState state = UNINITIALIZED;
/**
* List of all Module instances that we are going to manage.
*/
private Set<ModuleLifecycleTracker<T>> trackers =
new CopyOnWriteArraySet<ModuleLifecycleTracker<T>>();
/**
* List of Module instances that have successfully started. This is
* mainly used to allow us to stop the Modules in the reverse order.
*/
private List<ModuleLifecycleTracker<T>> started =
new CopyOnWriteArrayList<ModuleLifecycleTracker<T>>();
/**
* List of our ModuleLifecycleListeners.
*/
private List<ModuleLifecycleListener> mlListeners =
new CopyOnWriteArrayList<ModuleLifecycleListener>();
/**
* List of our ModuleLifecycleManagerListeners.
*/
private List<ModuleLifecycleManagerListener> mlmListeners =
new CopyOnWriteArrayList<ModuleLifecycleManagerListener>();
/**
* Flag indicating that a check is currently running. This allows the
* critical section to be protected without requiring synchronization
* during processing.
*/
private boolean checkRunning;
/**
* Proxy listener implementation to prevent exposing these methods
* as publicly accessible methods of the manager class.
*/
private ModuleLifecycleListener proxy = new ModuleLifecycleListener() {
/**
* {@inheritDoc}
*/
public void unhandledPeerGroupException(
ModuleLifecycleTracker subject, PeerGroupException mlcx) {
for (ModuleLifecycleListener listener : mlListeners) {
listener.unhandledPeerGroupException(subject, mlcx);
}
}
/**
* {@inheritDoc}
*/
public void moduleLifecycleStateUpdated(
ModuleLifecycleTracker subject, ModuleLifecycleState newState) {
for (ModuleLifecycleListener listener : mlListeners) {
listener.moduleLifecycleStateUpdated(subject, newState);
}
}
};
///////////////////////////////////////////////////////////////////////////
// Constructors:
/**
* Creates a new instance of ModuleLifecycleManager.
*/
public ModuleLifecycleManager() {
// Empty
}
///////////////////////////////////////////////////////////////////////////
// Public methods:
/**
* Add the specified ModuleLifecycleListener to our list of
* mlListeners to be notified when lifecycle events take place.
*
* @param listener listener to add
*/
public void addModuleLifecycleListener(ModuleLifecycleListener listener) {
mlListeners.add(listener);
}
/**
* Remove the specified ModuleLifecycleListener from our list of
* mlListeners.
*
* @param listener listener to remove
*/
public void removeModuleLifecycleListener(
ModuleLifecycleListener listener) {
mlListeners.remove(listener);
}
/**
* Add the specified ModuleLifecycleManagerListener to our list of
* listeners to be notified when lifecycle events take place.
*
* @param listener listener to add
*/
public void addModuleLifecycleManagerListener(
ModuleLifecycleManagerListener listener) {
mlmListeners.add(listener);
}
/**
* Remove the specified ModuleLifecycleManagerListener from our list of
* listeners.
*
* @param listener listener to remove
*/
public void removeModuleLifecycleListener(
ModuleLifecycleManagerListener listener) {
mlmListeners.remove(listener);
}
/**
* Adds the Module provided. Once it has been added, it
* will start progressing toward the current master state.
*
* @param subordinate the Module to be managed
* @param peerGroup the PeerGroup within which the master Module
* is operating.
* @param assignedID the ID assigned to this Module
* @param advertisement the Module's advertisement
* @param args arguments to pass to the tracker when it is started
*/
public void addModule(T subordinate, PeerGroup peerGroup,
ID assignedID, Advertisement advertisement, String[] args) {
ModuleLifecycleTracker<T> tracker =
new ModuleLifecycleTracker<T>(
subordinate, peerGroup, assignedID, advertisement, args);
tracker.addModuleLifecycleListener(proxy);
if (trackers.add(tracker)) {
if (tracker.getState() == STARTED) {
started.add(tracker);
}
checkTrackers();
}
}
/**
* Removes the specified Module from the auspices of the manager.
*
* @param subordinate the previously added Module which should be removed
* from the tracked Module list
* @param stopFirst set to true if the Module should always be returned to
* a stopped state, false to leave the Module in it's current state
*/
public void removeModule(Module subordinate, boolean stopFirst) {
for (ModuleLifecycleTracker tracker : trackers) {
Module module = tracker.getModule();
if (subordinate.equals(module)) {
// Stop on request
if (stopFirst) {
tracker.stopApp();
}
tracker.removeModuleLifecycleListener(proxy);
// Make sure we no longer track this Module as started
started.remove(tracker);
trackers.remove(tracker);
break;
}
}
}
/**
* Returns the current list of ModuleLifecycleTrackers.
*
* @return list of ModuleLifecycleTrackers
*/
public Set<ModuleLifecycleTracker<T>> getModuleLifecycleTrackers() {
return trackers;
}
/**
* Set the goal state to initialize the subordinate modules.
*/
public void init() {
setState(INITIALIZED);
}
/**
* Set the goal state to start the subordinate modules.
*/
public void start() {
setState(STARTED);
}
/**
* Set the goal state to stop the subordinate modules.
*/
public void stop() {
setState(STOPPED);
}
/**
* Convenience method to obtain the total number of Modules being tracked.
*
* @return number of tracked Modules
*/
public int getModuleCount() {
return trackers.size();
}
/**
* Gets the current goal state. This is the state which all suboridnate
* modules are attempting to get to, though they may not all be at this
* state.
*
* @return current goal state
*/
public ModuleLifecycleState getGoalState() {
synchronized(this) {
return state;
}
}
/**
* Returns the number of subordinate modules that are currently at
* the goal state. This method knows to check the ModuleLifecycle
* state's <code>isInitialized</code> and <code>isStarted</code> methods
* to match the intent of the goal state.
*
* @return number of subordinate Modules in the goal state
*/
public int getModuleCountInGoalState() {
ModuleLifecycleState goalState = getGoalState();
int result = 0;
for (ModuleLifecycleTracker tracker : trackers) {
ModuleLifecycleState tState = tracker.getState();
switch(goalState) {
case INITIALIZED:
if (tState != UNINITIALIZED) {
result++;
}
break;
case STOPPED:
if (tState != STARTED) {
result++;
}
break;
default:
// All other states are assumed to require direct equality
if (tState == goalState) {
result++;
}
break;
}
}
return result;
}
///////////////////////////////////////////////////////////////////////////
// Private methods:
/**
* Notify all manager listeners of a stalled module.
*
* @param tracker the tracker containing the stalled Module
*/
private void fireModuleStalled(ModuleLifecycleTracker tracker) {
for (ModuleLifecycleManagerListener listener : mlmListeners) {
listener.moduleStalled(this, tracker);
}
}
/**
* Notify all manager listeners of adisabled module.
*
* @param tracker the tracker containing the disabled Module
*/
private void fireModuleDisabled(ModuleLifecycleTracker tracker) {
for (ModuleLifecycleManagerListener listener : mlmListeners) {
listener.moduleDisabled(this, tracker);
}
}
/**
* Set the goal state to the specified state.
*
* @param newState new goal state
*/
private void setState(ModuleLifecycleState newState) {
synchronized(this) {
if (newState == state) {
// Nothing to do
return;
}
state = newState;
}
checkTrackers();
}
/**
* Check the trackers and see if any can be progressed toward the goalState
* state.
*/
private void checkTrackers() {
ModuleLifecycleState goalState;
// Gain exclusive processing access
synchronized(this) {
while(checkRunning) {
try {
wait();
} catch (InterruptedException intx) {
Thread.interrupted();
}
}
checkRunning = true;
goalState = state;
}
/*
* Each state has it's own way of being checked.
*/
switch (goalState) {
case UNINITIALIZED:
// Ignore this goalState
break;
case INITIALIZED:
checkTrackersInit();
break;
case STARTED:
checkTrackersStart();
break;
case STOPPED:
checkTrackersStop();
break;
default:
throw(new IllegalStateException(
"Unsupported goal state: " + goalState));
}
// Release processing access
synchronized(this) {
checkRunning = false;
notifyAll();
}
}
/**
* Perform tracker initialization.
*/
private void checkTrackersInit() {
for (ModuleLifecycleTracker tracker : trackers) {
tracker.init();
}
}
/**
* Perform tracker startup. This is the only goal state which can
* have different results requiring retries.
*/
private void checkTrackersStart() {
List<ModuleLifecycleTracker> stalled =
new ArrayList<ModuleLifecycleTracker>();
boolean progress;
int stalls = 0;
int maxIterations = trackers.size() * trackers.size();
int iteration = 0;
do {
List<ModuleLifecycleTracker> disabled = null;
progress = false;
stalled.clear();
for (ModuleLifecycleTracker<T> tracker : trackers) {
if (tracker.getState() == STARTED) {
// Ignore modules which are already started
continue;
}
int result = tracker.startApp();
if (result == Module.START_OK) {
started.add(tracker);
progress = true;
} else if (result == Module.START_AGAIN_PROGRESS) {
progress = true;
stalled.add(tracker);
} else if (result == Module.START_DISABLED) {
if (disabled == null) {
// Lazy init
disabled = new ArrayList<ModuleLifecycleTracker>();
}
disabled.add(tracker);
progress = true;
} else {
// some other error, including START_AGAIN_STALLED
stalled.add(tracker);
}
}
// Check for and dereference disabled modules
if (disabled != null) {
for (ModuleLifecycleTracker tracker : disabled) {
fireModuleDisabled(tracker);
tracker.removeModuleLifecycleListener(proxy);
trackers.remove(tracker);
}
}
// Check to see if we're done
if (stalled.size() == 0) {
break;
}
// Too many attempts?
if (++iteration >= maxIterations) {
for (ModuleLifecycleTracker tracker : stalled) {
fireModuleStalled(tracker);
}
break;
}
// If we made progress, retry immediately
if (progress) {
stalls = 0;
continue;
}
// Too many consecutive stalls?
if (++stalls >= MAX_STALLS) {
for (ModuleLifecycleTracker tracker : stalled) {
fireModuleStalled(tracker);
}
break;
}
} while (true);
}
/**
* Perform tracker shutdown.
*/
private void checkTrackersStop() {
Collections.reverse(started);
for (ModuleLifecycleTracker tracker : started) {
tracker.stopApp();
}
started.clear();
}
}