package org.oddjob.jmx;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServerConnection;
import org.oddjob.Structural;
import org.oddjob.jmx.client.ClientSession;
import org.oddjob.jmx.client.ClientSessionImpl;
import org.oddjob.jmx.client.RemoteLogPoller;
import org.oddjob.jmx.client.ServerView;
import org.oddjob.jmx.server.OddjobMBeanFactory;
import org.oddjob.jobs.job.StopJob;
import org.oddjob.logging.ConsoleArchiver;
import org.oddjob.logging.LogArchiver;
import org.oddjob.logging.LogLevel;
import org.oddjob.logging.LogListener;
import org.oddjob.structural.ChildHelper;
import org.oddjob.structural.StructuralListener;
/**
* @oddjob.description Connect to an Oddjob {@link org.oddjob.jmx.JMXServerJob}.
* This job allows remote jobs to be monitored and controlled from
* a local Oddjob.
* <p>
* This service will run until it is manually stopped or until the connection
* to the remote server is lost. If this job is stopped it's state will be
* COMPLETE, if the connection is lost the state state will be EXCEPTION.
* <p>
* To access and control jobs on a server from within a configuration file this
* client must have an id. If the client has an id of <code>'freds-pc'</code>
* and the job on the server has an id of <code>'freds-job'</code>. The job on
* the server can be accessed from the client using the expression
* <code>${freds-pc/freds-job}</code>.
* <p>
*
* @oddjob.example
*
* Connect to a remote server that is using the Platform MBean Server. This
* example also demonstrates using the value of a remote jobs property.
*
* {@oddjob.xml.resource org/oddjob/jmx/PlatformMBeanClientExample.xml}
*
* Note that the {@link StopJob} is required otherwise Oddjob wouldn't exit. An
* Alternative to using stop, would be to make the client a child of a
* {@link ServiceManager} job.
* <p>
* Here's an example of the command used to launch it:
* <pre>
* java -jar C:\Users\rob\projects\oddjob\run-oddjob.jar -f C:\Users\rob\projects\oddjob\test\java\org\oddjob\jmx\PlatformMBeanClientExample.xml localhost:13013
* </pre>
*
* This configuration is the client side of the first example in
* {@link JMXServerJob}.
*
* @oddjob.example
*
* To create a connection to a remote server that is using an RMI registry
* using the full form of the JMX URL.
*
* {@oddjob.xml.resource org/oddjob/jmx/ClientExample.xml}
*
* @oddjob.example
*
* Connect, run a remote job, and disconnect.
*
* {@oddjob.xml.resource org/oddjob/jmx/ClientRunsServerJob.xml}
*
* The run job starts the server job but doesn't wait for it to complete.
* We would need to add a wait job for that.
*
* @oddjob.example
*
* Connect using a username and password to a secure server.
*
* {@oddjob.xml.resource org/oddjob/jmx/SecureClientExample.xml}
*
* @oddjob.example
*
* A local job triggers when a server job runs.
*
* {@oddjob.xml.resource org/oddjob/jmx/ClientTrigger.xml}
*
*
* @author Rob Gordon
*/
public class JMXClientJob extends ClientBase
implements Structural, LogArchiver, ConsoleArchiver, RemoteDirectoryOwner {
public static final long DEFAULT_LOG_POLLING_INTERVAL = 5000;
/** The log poller thread */
private RemoteLogPoller logPoller;
/** Child helper */
private ChildHelper<Object> childHelper = new ChildHelper<Object>(this);
/** The client session */
private ClientSession clientSession;
/** View of the main server bean. */
private ServerView serverView;
/**
* @oddjob.property
* @oddjob.description The maximum number of log lines to retrieve for any
* component.
* @oddjob.required No.
*/
private int maxLoggerLines = LogArchiver.MAX_HISTORY;
/**
* @oddjob.property
* @oddjob.description The maximum number of console lines to retrieve for any
* component.
* @oddjob.required No.
*/
private int maxConsoleLines = LogArchiver.MAX_HISTORY;
/**
* @oddjob.property
* @oddjob.description The number of milliseconds between polling for new
* log events. Defaults to 5.
* @oddjob.required No.
*/
private long logPollingInterval = 5000;
/**
* @oddjob.property url
* @oddjob.description This property is now deprecated in favour of
* connection which reflects that the connection string no longer need
* only be a full JMX URL.
* @oddjob.required No.
*/
@Deprecated
public void setUrl(String url) {
setConnection(url);
}
/* (non-Javadoc)
* @see org.oddjob.logging.LogArchiver#addLogListener(org.oddjob.logging.LogListener, java.lang.String, org.oddjob.logging.LogLevel, long, long)
*/
@Override
public void addLogListener(LogListener l, Object component, LogLevel level,
long last, int history) {
stateHandler().assertAlive();
if (logPoller == null) {
throw new NullPointerException("logPoller not available");
}
logPoller.addLogListener(l, component, level, last, history);
// force poller to poll.
synchronized (logPoller) {
logPoller.notifyAll();
}
}
/* (non-Javadoc)
* @see org.oddjob.logging.LogArchiver#removeLogListener(org.oddjob.logging.LogListener)
*/
@Override
public void removeLogListener(LogListener l, Object component) {
if (logPoller == null) {
// must have been shut down.
return;
}
logPoller.removeLogListener(l, component);
}
/* (non-Javadoc)
* @see org.oddjob.logging.ConsoleArchiver#addConsoleListener(org.oddjob.logging.LogListener, java.lang.Object, long, int)
*/
@Override
public void addConsoleListener(LogListener l, Object component, long last,
int max) {
stateHandler().assertAlive();
if (logPoller == null) {
throw new NullPointerException("logPoller not available");
}
logPoller.addConsoleListener(l, component, last, max);
// force main thread to poll.
synchronized (this) {
notifyAll();
}
}
/* (non-Javadoc)
* @see org.oddjob.logging.ConsoleArchiver#removeConsoleListener(org.oddjob.logging.LogListener, java.lang.Object)
*/
@Override
public void removeConsoleListener(LogListener l, Object component) {
if (logPoller == null) {
// must have been shut down.
return;
}
logPoller.removeConsoleListener(l, component);
}
/* (non-Javadoc)
* @see org.oddjob.logging.ConsoleArchiver#consoleIdFor(java.lang.Object)
*/
@Override
public String consoleIdFor(Object component) {
return logPoller.consoleIdFor(component);
}
@Override
public void onInitialised() {
if (maxConsoleLines == 0) {
maxConsoleLines = LogArchiver.MAX_HISTORY;
}
if (maxLoggerLines == 0) {
maxLoggerLines = LogArchiver.MAX_HISTORY;
}
if (logPollingInterval == 0) {
logPollingInterval = DEFAULT_LOG_POLLING_INTERVAL;
}
}
/**
*
* @throws Exception
*/
@Override
protected void doStart(MBeanServerConnection mbsc,
ScheduledExecutorService notificationProcessor)
throws Exception {
clientSession = new ClientSessionImpl(
mbsc,
notificationProcessor,
getArooaSession(),
logger());
Object serverMain = clientSession.create(
OddjobMBeanFactory.objectName(0));
if (serverMain == null) {
throw new NullPointerException("No Oddjob MBean found.");
}
serverView = new ServerView(serverMain);
this.logPoller = new RemoteLogPoller(serverMain,
maxConsoleLines, maxLoggerLines);
serverView.startStructural(childHelper);
notificationProcessor.scheduleAtFixedRate(new Runnable() {
public void run() {
try {
serverView.noop();
} catch (RuntimeException e) {
try {
doStop(WhyStop.HEARTBEAT_FAILURE, e);
} catch (Exception e1) {
logger().error("Failed to stop.", e1);
}
}
}
@Override
public String toString() {
return "Heartbeat";
}
}, getHeartbeat(), getHeartbeat(), TimeUnit.MILLISECONDS);
logPoller.setLogPollingInterval(logPollingInterval);
Thread t = new Thread(logPoller);
t.start();
}
@Override
protected void onStop(final WhyStop why) {
logPoller.stop();
// if not destroyed by remote peer
if (why == WhyStop.STOP_REQUEST) {
clientSession.destroy(serverView.getProxy());
}
childHelper.removeAllChildren();
clientSession.destroyAll();
logPoller = null;
}
@Override
public RemoteDirectory provideBeanDirectory() {
if (serverView == null) {
return null;
}
return serverView.provideBeanDirectory();
}
/* (non-Javadoc)
* @see org.oddjob.Structural#addStructuralListener(org.oddjob.structural.StructuralListener)
*/
@Override
public void addStructuralListener(StructuralListener listener) {
childHelper.addStructuralListener(listener);
}
/* (non-Javadoc)
* @see org.oddjob.Structural#removeStructuralListener(org.oddjob.structural.StructuralListener)
*/
@Override
public void removeStructuralListener(StructuralListener listener) {
childHelper.removeStructuralListener(listener);
}
public int getMaxConsoleLines() {
return maxConsoleLines;
}
public void setMaxConsoleLines(int maxConsoleLines) {
this.maxConsoleLines = maxConsoleLines;
}
public int getMaxLoggerLines() {
return maxLoggerLines;
}
public void setMaxLoggerLines(int maxLoggerLines) {
this.maxLoggerLines = maxLoggerLines;
}
public long getLogPollingInterval() {
return logPollingInterval;
}
public void setLogPollingInterval(long logPollingInterval) {
this.logPollingInterval = logPollingInterval;
}
}