package org.oddjob.jmx;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import org.apache.log4j.Logger;
import org.oddjob.OddjobException;
import org.oddjob.arooa.ArooaSession;
import org.oddjob.arooa.deploy.annotations.ArooaAttribute;
import org.oddjob.arooa.deploy.annotations.ArooaHidden;
import org.oddjob.arooa.life.ArooaSessionAware;
import org.oddjob.arooa.registry.BeanDirectory;
import org.oddjob.arooa.registry.ServerId;
import org.oddjob.jmx.server.HandlerFactoryProvider;
import org.oddjob.jmx.server.OddjobMBeanFactory;
import org.oddjob.jmx.server.ResourceFactoryProvider;
import org.oddjob.jmx.server.ServerContextMain;
import org.oddjob.jmx.server.ServerInterfaceManagerFactoryImpl;
import org.oddjob.jmx.server.ServerLoopBackException;
import org.oddjob.jmx.server.ServerMainBean;
import org.oddjob.jmx.server.ServerModelImpl;
import org.oddjob.jmx.server.SimpleServerSecurity;
import org.oddjob.util.SimpleThreadManager;
import org.oddjob.util.ThreadManager;
/**
* @oddjob.description A service which allows a job hierarchy to
* be monitored and managed remotely using a {@link JMXClientJob}.
* <p>
* Security can be added using the environment property. Simple JMX security comes
* prepackaged as {@link SimpleServerSecurity}. Note that the access file is
* an Oddjob specific access file. Oddjob requires full read/write access because
* it uses JMX operations and all JMX operation require full read/write access.
* Oddjob uses a JMX access format file but provides it's own primitive access
* control on top the JMX layer. Oddjob's access control removes an entire java
* interface from the client side proxy if any of it's methods are write.
* One affect of this is that a read only account can't access properties of
* the remote job with the ${server/remote-job} syntax because this functionality
* is provided by the same interface (BeanUtils <code>DynaBean</code>) that allows
* a remote job's properties to be written.
* <p>
* For more information on JMX Security see
* <a href="http://java.sun.com/javase/6/docs/technotes/guides/jmx/tutorial/security.html">
* The JMX Tutorial</a>.
* <p>
* This service will use the Platform MBeanServer if no <code>url</code>
* property is provided. Creating an unsecured Oddjob server on a private
* network can be achieved simply by launching Oddjob with a command line
* such as:</p>
* <pre>
* java -Dcom.sun.management.jmxremote.port=nnnn \
* -Dcom.sun.management.jmxremote.ssl=false \
* -Dcom.sun.management.jmxremote.authenticate=false \
* -jar run-oddjob.jar -f my-config.xml
* </pre>
* And then including this service somewhere in the configuration. Note that
* the properties must be to the left of the -jar, not to the right because
* the must be available to the JVM before Oddjob starts.
* <p>
* The <code>server.xml</code> Oddjob configration file in Oddjob's top
* level directory provides a simple Oddjob server that uses an RMI
* Registry.
* <p>
* More information on Oddjob servers can be found in the User Guide under
* 'Sharing Jobs on the Network'.
* <p>
*
* @oddjob.example
*
* Creating a server using the platform MBean Server.
*
* {@oddjob.xml.resource org/oddjob/jmx/PlatformMBeanServerExample.xml}
*
* This is probably the simplest way to launch Oddjob as a server.
* <p>
* Here's an example of the command used to launch it:
* <pre>
* java -Dcom.sun.management.jmxremote.port=13013 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar C:\Users\rob\projects\oddjob\run-oddjob.jar -f C:\Users\rob\projects\oddjob\test\java\org\oddjob\jmx\PlatformMBeanServerExample.xml
* </pre>
* For an example of a client to connect to this server see the first
* example for {@link JMXClientJob}.
*
* @oddjob.example
*
* Creating a server using an RMI registry.
*
* {@oddjob.xml.resource org/oddjob/jmx/ServerExample.xml}
*
* The nested Oddjob can be any normal Oddjob configuration. Here is the
* nested Oddjob used in some client examples. The greeting is in
* a folder because it will only be run from the client.
*
* {@oddjob.xml.resource org/oddjob/jmx/ServerJobs.xml}
*
* @oddjob.example
*
* Creating a secure server.
*
* {@oddjob.xml.resource org/oddjob/jmx/SecureServerExample.xml}
*
* @author Rob Gordon
*/
public class JMXServerJob implements ArooaSessionAware {
private static final Logger logger = Logger.getLogger(JMXServerJob.class);
public static final String ACCESS_FILE_PROPERTY = "oddjob.jmx.remote.x.access.file";
/**
* @oddjob.property
* @oddjob.description A name, can be any text.
* @oddjob.required No.
*/
private String name;
/**
* @oddjob.property
* @oddjob.description The root node.
* @oddjob.required Yes.
*/
private Object root;
/**
* @oddjob.property
* @oddjob.description The JMX service URL. If this is not provided the
* server connects to the Platform MBean Server.
* @oddjob.required No.
*/
private String url;
/**
* @oddjob.property
* @oddjob.description The log format for formatting log messages. For more
* information on the format please see <a href="http://logging.apache.org/log4j/docs/">
* http://logging.apache.org/log4j/docs/</a>
* @oddjob.required No.
*/
private String logFormat;
/**
* @oddjob.property
* @oddjob.description Additional handler factories that allow
* any interface to be invoked from a remote Oddjob.
*
* @oddjob.required No.
*/
private HandlerFactoryProvider handlerFactories;
/**
* The ThreadManager. Handlers use this to avoid long running
* connections.
*/
private ThreadManager threadManager;
/**
* @oddjob.property
* @oddjob.description The address of this server. This is mainly
* useful for testing
*/
private String address;
/** Remember the registry so it can be used to resolve ids for the
* client.
*/
private ArooaSession session;
/** Bean Factory */
private OddjobMBeanFactory factory;
/** Main Server MBean name */
private ObjectName mainName;
/** Connector server */
private JMXConnectorServer cntorServer;
/**
* @oddjob.property
* @oddjob.description An environment such
* as security settings.
*
* @oddjob.required No.
*/
private Map<String, ?> environment;
@ArooaHidden
public void setArooaSession(ArooaSession session) {
this.session = session;
}
/**
* Get the name.
*
* @return The name.
*/
public String getName() {
return name;
}
/**
* Set the name
*
* @param name The name.
*/
public void setName(String name) {
this.name = name;
}
/**
* Set the root node directly.
*
* @param rootNode The root node for the monitor tree.
*/
@ArooaAttribute
public void setRoot(Object rootNode) {
this.root = rootNode;
}
/**
* Get the root node of the monitor tree.
*
* @return The root node.
*/
public Object getRoot() {
return this.root;
}
public String getAddress() {
return address;
}
/**
* Set the name to bind the root node as in the naming service.
*
* @param url The name for the naming service.
*/
public void setUrl(String bindAs) {
this.url = bindAs;
}
/**
* Get the name the root node is bound as in the naming service.
*
* @return The name used in the naming service.
*/
public String getUrl() {
return url;
}
public void start()
throws JMException, MalformedURLException, IOException, ServerLoopBackException {
if (root == null) {
throw new OddjobException("No root node.");
}
ServerStrategy serverStrategy = ServerStrategy.stratagyFor(url);
MBeanServer server = serverStrategy.findServer();
threadManager = new SimpleThreadManager();
// Add supported interfaces.
// note that some interface are hardwired in the factory because
// they are aspects of the server.
ServerInterfaceManagerFactoryImpl imf =
new ServerInterfaceManagerFactoryImpl(environment);
imf.addServerHandlerFactories(
new ResourceFactoryProvider(session
).getHandlerFactories());
if (handlerFactories != null) {
imf.addServerHandlerFactories(handlerFactories.getHandlerFactories());
}
BeanDirectory registry = session.getBeanRegistry();
ServerModelImpl model = new ServerModelImpl(
new ServerId(serverStrategy.serverIdText()),
threadManager,
imf);
model.setLogFormat(logFormat);
factory = new OddjobMBeanFactory(server, session);
ServerMainBean serverBean = new ServerMainBean(
root,
registry);
mainName = factory.createMBeanFor(serverBean,
new ServerContextMain(model, registry));
this.cntorServer = serverStrategy.startConnector(environment);
this.address = serverStrategy.getAddress();
}
/**
*
* @throws Exception
*/
public void stop() throws Exception {
logger.debug("Stopping any running jobs.");
threadManager.close();
logger.debug("Desroying MBeans.");
try {
factory.destroy(mainName);
}
catch (JMException e) {
// This can happen when the RMI registry is shut before the
// server is stopped.
logger.error("Failed destroying main MBean.", e);
}
if (cntorServer != null) {
logger.debug("Stopping JMXConnectorServer.");
this.address = null;
cntorServer.stop();
}
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString() {
if (name == null) {
return "Oddjob Server";
}
return name;
}
public String getLogFormat() {
return logFormat;
}
public void setLogFormat(String logFormat) {
this.logFormat = logFormat;
}
public HandlerFactoryProvider getHandlerFactories() {
return handlerFactories;
}
public void setHandlerFactories(HandlerFactoryProvider handlerFactories) {
this.handlerFactories = handlerFactories;
}
public Map<String, ?> getEnvironment() {
return environment;
}
public void setEnvironment(Map<String, ?> environment) {
this.environment = environment;
}
}