/*------------------------------------------------------------------------------
Name: RunlevelManager.java
Project: xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.engine.runlevel;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.xmlBlaster.util.FileLocator;
import org.xmlBlaster.util.MsgUnit;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.admin.extern.JmxMBeanHandle;
import org.xmlBlaster.util.classloader.StandaloneClassLoaderFactory;
import org.xmlBlaster.util.context.ContextNode;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.engine.ServerScope;
import org.xmlBlaster.engine.qos.PublishQosServer;
import org.xmlBlaster.authentication.Authenticate;
import org.xmlBlaster.authentication.SessionInfo;
import java.util.Properties;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.Collections;
import java.util.Iterator;
import org.xmlBlaster.util.plugin.PluginInfo;
import org.xmlBlaster.util.plugin.I_Plugin;
/**
* This starts/stops xmlBlaster with different run levels.
* <p>
* @author <a href="mailto:xmlBlaster@marcelruff.info">Marcel Ruff</a>
* @see <a href="http://www.xmlblaster.org/xmlBlaster/doc/requirements/engine.runlevel.html">engine.runlevel requirement</a>
*/
public final class RunlevelManager implements RunlevelManagerMBean
{
private String ME = "RunlevelManager";
private final ServerScope glob;
private static Logger log = Logger.getLogger(RunlevelManager.class.getName());
private int currRunlevel = 0;
public static final int RUNLEVEL_HALTED_PRE = -1;
public static final int RUNLEVEL_HALTED = 0;
public static final int RUNLEVEL_HALTED_POST = 1;
public static final int RUNLEVEL_STANDBY_PRE = 2;
public static final int RUNLEVEL_STANDBY = 3;
public static final int RUNLEVEL_STANDBY_POST = 4;
public static final int RUNLEVEL_CLEANUP_PRE = 5;
public static final int RUNLEVEL_CLEANUP = 6;
public static final int RUNLEVEL_CLEANUP_POST = 7;
public static final int RUNLEVEL_RUNNING_PRE = 8;
public static final int RUNLEVEL_RUNNING = 9;
public static final int RUNLEVEL_RUNNING_POST = 10;
private final I_RunlevelListener[] DUMMY_ARR = new I_RunlevelListener[0];
/** My JMX registration */
private JmxMBeanHandle mbeanHandle;
private ContextNode contextNode;
private boolean allowDynamicPlugins;
/**
* For listeners who want to be informed about runlevel changes.
*/
private final Set runlevelListenerSet = Collections.synchronizedSet(new HashSet());
/**
* One instance of this represents one xmlBlaster server.
* <p />
* You need to call initPluginManagers() after creation.
*/
public RunlevelManager(ServerScope glob) {
this.glob = glob;
this.ME = "RunlevelManager" + this.glob.getLogPrefixDashed();
if (log.isLoggable(Level.FINER)) log.finer("Incarnated run level manager");
// For JMX instanceName may not contain ","
this.contextNode = new ContextNode(ContextNode.SERVICE_MARKER_TAG,
"RunlevelManager", this.glob.getScopeContextNode());
this.allowDynamicPlugins = this.glob.getProperty().get("xmlBlaster/allowDynamicPlugins", false);
if (allowDynamicPlugins) {
String text = "xmlBlaster/allowDynamicPlugins=true: Please protect this feature as any code can be injected, add preventive authorization settings, see http://www.xmlBlaster.org/xmlBlaster/doc/requirements/engine.runlevel.howto.html#dynamic";
log.warning(text);
}
}
// Is done from external engine.Global to avoid
public void initJmx() {
try {
this.mbeanHandle = this.glob.registerMBean(this.contextNode, this);
}
catch(XmlBlasterException e) {
log.severe(e.getMessage());
}
}
public ContextNode getContextNode() {
return this.contextNode;
}
/**
* Sets the cluster node ID as soon as it is known.
*/
public void setId(String id) {
this.ME = "RunlevelManager" + this.glob.getLogPrefixDashed();
}
/**
* Incarnate the different managers which handle run levels.
*/
public void initPluginManagers() throws XmlBlasterException {
// TODO: This should be configurable
new Authenticate(glob);
// glob.getProtocolManager(); // force incarnation
if (log.isLoggable(Level.FINER)) log.finer("Initialized run level manager");
}
/**
* Called from RequestBroker when a new plugin XML is arriving.
* <br />
* Allows to send dynamically new plugins
* <br />
* java javaclients.HelloWorldPublish
* -oid __sys__RunlevelManager
* -contentFile dynamic.jar
* -clientProperty[__plugin.jarName] dynamic.jar
* -clientProperty[__plugin.xml] "<plugin id='DynamicPlugin' className='javaclients.DynamicPlugin'><action do='LOAD' onStartupRunlevel='3' sequence='0' onFail='resource.configuration.pluginFailed'/><action do='STOP' onShutdownRunlevel='6' sequence='4'/></plugin>"
* @param sessionInfo The publisher
* @param msgUnit The content contains the jar file, and some client properties the configuration
* @param publishQos
* @return
* @throws XmlBlasterException
*/
public final String publish(SessionInfo sessionInfo, MsgUnit msgUnit, PublishQosServer publishQos) throws XmlBlasterException {
PluginConfigSaxFactory factory = new PluginConfigSaxFactory(this.glob);
// TODO: Send the jar in the content, and the XML in clientProperty
String jarName = msgUnit.getQosData().getClientProperty(Constants.CLIENTPROPERTY_PLUGIN_JARNAME, "dynamicPlugin.jar");
if (!this.allowDynamicPlugins) {
String text = "xmlBlaster/allowDynamicPlugins=false: Your dynamic plugin '"
+ jarName + "' is rejected, see http://www.xmlBlaster.org/xmlBlaster/doc/requirements/engine.runlevel.howto.html#dynamic";
log.warning(text);
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_CONFIGURATION, ME, text);
}
String xml = msgUnit.getQosData().getClientProperty(Constants.CLIENTPROPERTY_PLUGIN_XML, "");
if (xml == null) {
String text = "Missing '" + Constants.CLIENTPROPERTY_PLUGIN_XML + "' with plugin registration markup as in xmlBlasterPlugins.xml";
log.warning(text);
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_CONFIGURATION, ME, text);
}
PluginConfig pluginConfig = factory.readObject(xml);
byte[] jarFile = msgUnit.getContent();
if (jarFile.length > 0) {
try {
String dir = StandaloneClassLoaderFactory.getDirectoryForWrite();
File file = (dir == null) ? File.createTempFile("dynamicPlugin", ".jar") : new File(dir, jarName);
FileLocator.writeFile(file.getAbsolutePath(), jarFile);
log.info("Writing new arrived jar file '" + file.getAbsolutePath() + "' for plugin id=" + pluginConfig.getId() + " with className=" + pluginConfig.getPluginInfo().getClassName());
//URLClassLoader loader = clFactory.getPluginClassLoader(pluginConfig.getPluginInfo());
// See StandaloneClassLoader.java:62
Properties params = pluginConfig.getPluginInfo().getParameters();
String classpath = params.getProperty(PluginInfo.KEY_CLASSPATH);
if (classpath == null || classpath.length() < 1)
classpath = file.getAbsolutePath();
else
classpath += ":"+file.getAbsolutePath();
params.put(PluginInfo.KEY_CLASSPATH, classpath);
//loader.newInstance(urls);
}
catch (IOException e) {
log.warning("Problems creating plugin " + pluginConfig.getId() +" with jar '" + jarName + "': " + e.toString());
}
}
addPlugin(pluginConfig);
return Constants.RET_OK;
}
/**
* Adds the specified runlevel listener to receive runlevel change events.
* Multiple registrations fo the same listener will overwrite the old one.
*/
public void addRunlevelListener(I_RunlevelListener l) {
if (l == null) {
return;
}
synchronized (runlevelListenerSet) {
runlevelListenerSet.add(l);
}
}
/**
* Removes the specified listener.
*/
public void removeRunlevelListener(I_RunlevelListener l) {
if (l == null) {
return;
}
synchronized (runlevelListenerSet) {
runlevelListenerSet.remove(l);
}
}
/**
* Allows to pass the newRunlevel as a String like "RUNLEVEL_STANDBY" or "6"
* @see #changeRunlevel(int, boolean)
*/
public final int changeRunlevel(String newRunlevel, boolean force) throws XmlBlasterException {
if (newRunlevel == null || newRunlevel.length() < 1) {
String text = "Runlevel " + newRunlevel + " is not allowed, please choose one of " +
RUNLEVEL_HALTED + "|" + RUNLEVEL_STANDBY + "|" +
RUNLEVEL_CLEANUP + "|" + RUNLEVEL_RUNNING;
if (log.isLoggable(Level.FINE)) log.fine(text);
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_CONFIGURATION, ME, text);
}
int level = 0;
try {
level = Integer.parseInt(newRunlevel.trim());
return glob.getRunlevelManager().changeRunlevel(level, true);
}
catch(NumberFormatException e) {
level = toRunlevelInt(newRunlevel);
return glob.getRunlevelManager().changeRunlevel(level, true);
}
}
/**
* JMX: Change the run level of xmlBlaster.
* @param 0 is halted and 9 is fully operational
*/
public String setRunlevel(String level) throws Exception {
try {
int numErrors = changeRunlevel(level, true);
return "Changed to run level " +toRunlevelStr(getCurrentRunlevel())+" '" + level + "'" +
((numErrors>0) ? (" with "+numErrors+" errors") : "");
}
catch (XmlBlasterException e) {
throw new Exception(e.getMessage());
}
}
/**
* Change the run level to the given newRunlevel.
* <p />
* See RUNLEVEL_HALTED etc.
* <p />
* Note that there are four main run levels:
* <ul>
* <li>RUNLEVEL_HALTED</li>
* <li>RUNLEVEL_STANDBY</li>
* <li>RUNLEVEL_CLEANUP</li>
* <li>RUNLEVEL_RUNNING</li>
* </ul>
* and every RUNLEVEL sends a pre and a post run level event, to allow
* the listeners to prepare or log before or after successfully changing levels.<br />
* NOTE that the pre/post events are <b>no</b> run level states - they are just events.
* @param newRunlevel The new run level we want to switch to
* @param force Ignore exceptions during change, currently only force == true is supported
* @return numErrors
* @exception XmlBlasterException for invalid run level
*/
public final int changeRunlevel(int newRunlevel, boolean force) throws XmlBlasterException {
if (log.isLoggable(Level.FINER)) log.finer("Changing from run level " + currRunlevel + " to run level " + newRunlevel + " with force=" + force);
long start = System.currentTimeMillis();
int numErrors = 0;
if (currRunlevel == newRunlevel) {
return numErrors;
}
int from = currRunlevel;
int to = newRunlevel;
log.info("Change request from run level " + toRunlevelStr(from) + " to run level " + toRunlevelStr(to) + " ...");
if (!isMajorLevel(to)) {
String text = "Runlevel " + to + " is not allowed, please choose one of " +
RUNLEVEL_HALTED + "|" + RUNLEVEL_STANDBY + "|" +
RUNLEVEL_CLEANUP + "|" + RUNLEVEL_RUNNING;
if (log.isLoggable(Level.FINE)) log.fine(text);
throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_CONFIGURATION, ME, text);
}
if (from < to) { // startup
for (int ii=from; ii<to; ii++) {
int dest = ii+1;
try {
startupPlugins(ii, dest);
fireRunlevelEvent(ii, dest, force);
}
finally {
currRunlevel = dest; // pre/post events are not marked as run levels
if (dest > from && isMajorLevel(dest)) {
long elapsed = System.currentTimeMillis() - start;
if (numErrors == 0)
log.fine("Successful startup to run level " + toRunlevelStr(dest) + Timestamp.millisToNice(elapsed));
else
log.info("Startup to run level " + toRunlevelStr(dest) + " done with " + numErrors + " errors.");
}
}
}
if (to == RUNLEVEL_RUNNING) { // Main.java to display banner
fireRunlevelEvent(RUNLEVEL_RUNNING, RUNLEVEL_RUNNING_POST, force);
}
}
else if (from > to) { // shutdown
for (int ii=from; ii>to; ii--) {
int dest = ii-1;
try {
shutdownPlugins(ii, dest);
fireRunlevelEvent(ii, dest, force);
}
finally {
currRunlevel = dest;
if (dest < from && isMajorLevel(dest)) {
long elapsed = System.currentTimeMillis() - start;
if (numErrors == 0)
log.fine("Successful shutdown to run level=" + toRunlevelStr(dest) + Timestamp.millisToNice(elapsed));
else
log.info("Shutdown to run level=" + toRunlevelStr(dest) + " done with " + numErrors + " errors.");
}
}
}
}
if (log.isLoggable(Level.FINER)) log.finer("Leaving changeRunlevel with runlevel = " + toRunlevelStr(currRunlevel));
return numErrors;
}
/**
*
*/
private void startupPlugins(int from, int to) throws XmlBlasterException {
TreeSet pluginSet = this.glob.getPluginHolder().getStartupSequence(this.glob.getStrippedId(), from+1, to);
if (log.isLoggable(Level.FINER)) log.finer("startupPlugins. the size of the plugin set is '" + pluginSet.size() + "'");
Iterator iter = pluginSet.iterator();
while (iter.hasNext()) {
PluginConfig pluginConfig = (PluginConfig)iter.next();
if (pluginConfig == null) {
log.warning("startupPlugins. the pluginConfig object is null");
continue;
}
if (!pluginConfig.isCreate()) {
log.fine("startupPlugins. the plugin + " + pluginConfig.getId() + " is ignored, create='false'");
continue;
}
if (log.isLoggable(Level.FINEST)) log.finest("startupPlugins " + pluginConfig.toXml());
try {
long startTime = System.currentTimeMillis();
PluginInfo pluginInfo = pluginConfig.getPluginInfo();
if (log.isLoggable(Level.FINER)) {
if (pluginInfo != null) {
log.finer("startupPlugins pluginInfo object: " + pluginInfo.getId() + " classname: " + pluginInfo.getClassName());
}
else log.finer("startupPlugins: the pluginInfo is null");
}
this.glob.getPluginManager().getPluginObject(pluginInfo);
long deltaTime = System.currentTimeMillis() - startTime;
log.fine("Run level '" + from + "' to '" + to + "' plugin '" + pluginConfig.getId() + "' successful loaded in '" + deltaTime + "' ms");
}
catch (Throwable ex) {
ErrorCode code = pluginConfig.getUpAction().getOnFail();
if (code == null) {
log.warning("Exception when loading the plugin '" + pluginConfig.getId() + "' reason: " + ex.toString());
ex.printStackTrace();
}
else {
throw new XmlBlasterException(this.glob, code, ME + ".startupPlugins", "Can't load plugin '" + pluginConfig.getId() + "'", ex);
}
}
}
}
/**
* Called by JMX, throws IllegalArgumentExcetion instead of XmlBlasterException.
* @param pluginConfig
* @param create
*/
void toggleCreate(PluginConfig pluginConfig, boolean create) {
log.info("Changing plugin '" + pluginConfig.getId() + "' create=" + pluginConfig.isCreate() + " to " + create);
if (pluginConfig.isCreate() != create) {
if (create) {
try {
this.glob.getPluginManager().getPluginObject(pluginConfig.getPluginInfo());
}
catch (XmlBlasterException e) {
log.warning("Failed to create plugin: " + e.toString());
throw new IllegalArgumentException("Failed to create plugin: " + e.toString());
}
catch (Throwable e) {
e.printStackTrace();
}
}
else {
try {
I_Plugin plugin = this.glob.getPluginManager().getPluginObject(pluginConfig.getPluginInfo());
plugin.shutdown();
this.glob.getPluginManager().removeFromPluginCache(pluginConfig.getPluginInfo().getId());
}
catch (XmlBlasterException e) {
log.warning("Failed to remove plugin: " + e.toString());
throw new IllegalArgumentException("Failed to create plugin: " + e.toString());
}
catch (Throwable e) {
e.printStackTrace();
}
}
}
}
/**
* Add a new plugin, if it exists remove the old first.
* @param pluginConfig
*/
private void addPlugin(PluginConfig pluginConfig) throws XmlBlasterException {
log.info("New runlevel plugin configuration arrived: " + pluginConfig.getPluginInfo().getId());
I_Plugin oldPlugin = this.glob.getPluginManager().removeFromPluginCache(pluginConfig.getPluginInfo().getId());
PluginHolder holder = this.glob.getPluginHolder();
PluginConfig oldConfig = holder.removePluginConfig(null, pluginConfig.getId());
if (oldConfig != null)
log.info("Removed old plugin " + oldConfig.getId());
if (oldPlugin != null && oldConfig == null)
log.severe("Unexpected plugin cache entry:" + oldPlugin.getType());
holder.addDefaultPluginConfig(pluginConfig);
pluginConfig.registerMBean();
if (pluginConfig.isCreate())
this.glob.getPluginManager().getPluginObject(pluginConfig.getPluginInfo());
}
/**
*
*/
private void shutdownPlugins(int from, int to) throws XmlBlasterException {
TreeSet pluginSet = this.glob.getPluginHolder().getShutdownSequence(this.glob.getStrippedId(), to, from-1);
Iterator iter = pluginSet.iterator();
while (iter.hasNext()) {
PluginConfig pluginConfig = (PluginConfig)iter.next();
if (pluginConfig == null || !pluginConfig.isCreate())
continue;
try {
PluginInfo pluginInfo = pluginConfig.getPluginInfo();
I_Plugin plugin = this.glob.getPluginManager().getPluginObject(pluginInfo);
plugin.shutdown();
this.glob.getPluginManager().removeFromPluginCache(pluginInfo.getId());
log.fine("fireRunlevelEvent: run level '" + from + "' to '" + to + "' plugin '" + pluginConfig.getId() + "' shutdown");
}
catch (Throwable ex) {
ErrorCode code = pluginConfig.getDownAction().getOnFail();
if (code == null) {
log.warning(".fireRunlevelEvent. Exception when shutting down the plugin '" + pluginConfig.getId() + "' reason: " + ex.toString());
}
else {
throw new XmlBlasterException(this.glob, code, ME + ".fireRunlevelEvent", ".fireRunlevelEvent. Exception when shutting down the plugin '" + pluginConfig.getId() + "'", ex);
}
}
}
}
/**
* The static plugins are loaded from (exclusive) to (inclusive) when startup and
* the same when shutting down. For example if you define LOAD on r 3, and STOP on
* r 2, then LOAD is fired when from=2,to=3 and STOP when from=3,to=2
*/
private final int fireRunlevelEvent(int from, int to, boolean force) throws XmlBlasterException {
int numErrors = 0;
// Take a snapshot of current listeners (to avoid ConcurrentModificationException in iterator)
I_RunlevelListener[] listeners;
synchronized (runlevelListenerSet) {
if (runlevelListenerSet.size() == 0)
return numErrors;
listeners = (I_RunlevelListener[])runlevelListenerSet.toArray(DUMMY_ARR);
}
for (int ii=0; ii<listeners.length; ii++) {
I_RunlevelListener li = listeners[ii];
try {
li.runlevelChange(from, to, force);
if (log.isLoggable(Level.FINE)) {
if (isMajorLevel(to)) {
if (from < to)
log.fine(li.getName() + " successful startup to run level=" + to + ", errors=" + numErrors + ".");
else
log.fine(li.getName() + " successful shutdown to run level=" + to + ", errors=" + numErrors + ".");
}
}
}
catch (XmlBlasterException e) {
if (e.isInternal()) {
log.severe("Changing from run level=" + from + " to level=" + to + " failed for component " + li.getName() + ": " + e.getMessage());
}
else {
log.warning("Changing from run level=" + from + " to level=" + to + " failed for component " + li.getName() + ": " + e.getMessage());
}
numErrors++;
}
}
return numErrors;
}
/**
* See java for runlevels
*/
public final int getCurrentRunlevel() {
return currRunlevel;
}
public boolean isHalted() {
return currRunlevel <= RUNLEVEL_HALTED;
}
public boolean isStandby() {
return currRunlevel == RUNLEVEL_STANDBY;
}
public boolean isCleanup() {
return currRunlevel == RUNLEVEL_CLEANUP;
}
public boolean isRunning() {
return currRunlevel == RUNLEVEL_RUNNING;
}
/**
* @return true if one of the major run levels. false if pre or post event level
*/
public boolean isMajorLevel() {
return isMajorLevel(currRunlevel);
}
//======== static methods ============
private static final boolean isMajorLevel(int level) {
if (level == RUNLEVEL_HALTED || level == RUNLEVEL_STANDBY ||
level == RUNLEVEL_CLEANUP || level == RUNLEVEL_RUNNING)
return true;
return false;
}
/**
* @return true if one of the major levels
*/
public static final boolean checkRunlevel(int level) {
return isMajorLevel(level);
}
/**
* Parses given string to extract the priority of a message
* @param level For example 7
* @return "RUNLEVEL_UNKNOWN" if no valid run level, else for
* example "STANDBY_POST"
*/
public final static String toRunlevelStr(int level) {
if (level == RUNLEVEL_HALTED_PRE)
return "HALTED_PRE";
else if (level == RUNLEVEL_HALTED)
return "HALTED";
else if (level == RUNLEVEL_HALTED_POST)
return "HALTED_POST";
else if (level == RUNLEVEL_STANDBY_PRE)
return "STANDBY_PRE";
else if (level == RUNLEVEL_STANDBY)
return "STANDBY";
else if (level == RUNLEVEL_STANDBY_POST)
return "STANDBY_POST";
else if (level == RUNLEVEL_CLEANUP_PRE)
return "CLEANUP_PRE";
else if (level == RUNLEVEL_CLEANUP)
return "CLEANUP";
else if (level == RUNLEVEL_CLEANUP_POST)
return "CLEANUP_POST";
else if (level == RUNLEVEL_RUNNING_PRE)
return "RUNNING_PRE";
else if (level == RUNLEVEL_RUNNING)
return "RUNNING";
else if (level == RUNLEVEL_RUNNING_POST)
return "RUNNING_POST";
else
return "RUNLEVEL_UNKNOWN(" + level + ")";
}
/**
* Parses given string to extract the priority of a message
* @param level For example "STANDBY" or 7
* @param defaultPriority Value to use if not parseable
* @return -10 if no valid run level
*/
public final static int toRunlevelInt(String level) {
if (level == null) return -10;
level = level.trim();
try {
return Integer.parseInt(level);
}
catch(NumberFormatException e) {}
if (level.equalsIgnoreCase("HALTED_PRE"))
return RUNLEVEL_HALTED_PRE;
else if (level.equalsIgnoreCase("HALTED"))
return RUNLEVEL_HALTED;
else if (level.equalsIgnoreCase("HALTED_POST"))
return RUNLEVEL_HALTED_POST;
else if (level.equalsIgnoreCase("STANDBY_PRE"))
return RUNLEVEL_STANDBY_PRE;
else if (level.equalsIgnoreCase("STANDBY"))
return RUNLEVEL_STANDBY;
else if (level.equalsIgnoreCase("STANDBY_POST"))
return RUNLEVEL_STANDBY_POST;
else if (level.equalsIgnoreCase("CLEANUP_PRE"))
return RUNLEVEL_CLEANUP_PRE;
else if (level.equalsIgnoreCase("CLEANUP"))
return RUNLEVEL_CLEANUP;
else if (level.equalsIgnoreCase("CLEANUP_POST"))
return RUNLEVEL_CLEANUP_POST;
else if (level.equalsIgnoreCase("RUNNING_PRE"))
return RUNLEVEL_RUNNING_PRE;
else if (level.equalsIgnoreCase("RUNNING"))
return RUNLEVEL_RUNNING;
else if (level.equalsIgnoreCase("RUNNING_POST"))
return RUNLEVEL_RUNNING_POST;
else
return -10;
}
public void shutdown() {
if (this.mbeanHandle != null)
this.glob.unregisterMBean(this.mbeanHandle);
}
/* (non-Javadoc)
* @see org.xmlBlaster.util.admin.I_AdminUsage#usage()
*/
public java.lang.String usage() {
return ServerScope.getJmxUsageLinkInfo(this.getClass().getName(), null);
}
/* (non-Javadoc)
* @see org.xmlBlaster.util.admin.I_AdminUsage#getUsageUrl()
*/
public java.lang.String getUsageUrl() {
return ServerScope.getJavadocUrl(this.getClass().getName(), null);
}
/* (non-Javadoc)
* JMX dummy to have a copy/paste functionality in jconsole
* @see org.xmlBlaster.util.admin.I_AdminUsage#setUsageUrl(java.lang.String)
*/
public void setUsageUrl(java.lang.String url) {}
}