Thanks to everyone's comments, we took a deeper look and found what we were looking for in JMX. We plan on leveraging a single JVM/Tomcat instance with seperate contexts. The Tomcat Admin web application has the MBeans we were looking for enabling us to dynamically add/configure data sources.
Original post
I have a requirement that calls for breaking the product I am designing into multiple JVMs (Tomcat instances to be precise). The original plan was to have a single Tomcat instance with multiple contexts however that plan had to be shelved for various reasons. One of the Tomcat instances is to support the Management GUI. To cut a long story short, the Management GUI is required to manage the life-cycle (i.e. stop/start) of another Tomcat instance containing the core engine of the product.
I explored several options for approaching this problem. My first attempt was to look at OSGi. I have been eager to explore OSGi for quite a while now so this gave me a perfect opportunity. I managed to knock out a couple of simple bundles and deploy them to Equinox. Next step, try and build a bundle out of Tomcat. Unfortunately, this didn’t quite go to plan. Tomcat seems to have an issue with authority – it wouldn’t play nicely in an OSGi world leaving me with a stack trace of exception I didn’t have the time or energy to track down. I did think about switching to Jetty at this point, but this would be a very disruptive change and one that our schedule couldn’t allow.
Moving on, I recalled that Eclipse WTP does a pretty good job in managing server definitions and controlling their lifecycles. I proceeded to download the source for WTP and found a bunch of code called Server Tools. I tried to reverse engineer the structure from the code but was quite astonished by the size of the code base. I figured it would be too much to weed through to get to what I was looking for. The Server Tools is pretty well structured code – it’s just overkill for my needs.
Next step, there has to be something in Ant I can use. I remember using the Ant Catalina tools from years ago. These are basically Ant tasks that expose the controls of Tomcat through the Manager application to Ant. The tasks didn’t really give me what I was looking for. Sure, they could be used to control individual contexts but didn’t really give me anything to control the server instance itself.
I was really looking to avoid Runtime.exec() if I could. There had to be something that worked at a higher level of abstraction. I continued to dig around Ant and recalled the Java
TomcatController.java
import java.io.File;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.Path;
public class TomcatController {
public enum State {
STARTING,
STARTED,
STOPPING,
STOPPED
};
State state = State.STOPPED;
State getState() {
return state;
}
private TomcatServerConfiguration config;
public TomcatController(TomcatServerConfiguration config) {
this.config = config;
}
// Asynchronously start this Tomcat
public synchronized void start() {
state = State.STARTING;
Java javaTask = createTomcatJavaTask();
javaTask.createArg().setValue("start");
for (String s : config.getJvmArgs())
javaTask.createJvmarg().setValue(s);
javaTask.setFork(true);
javaTask.setSpawn(true);
System.out.println(javaTask.getCommandLine());
javaTask.execute();
state = State.STARTED;// Not quite true - Tomcat may still be starting at this point
}
// Block while this Tomcat is stopping
public synchronized void stop() {
state = State.STOPPING;
Java javaTask = createTomcatJavaTask();
javaTask.createArg().setValue("stop");
javaTask.setFork(true);
javaTask.setSpawn(false);
System.out.println(javaTask.getCommandLine());
javaTask.execute();
state = State.STOPPED;
}
private Java createTomcatJavaTask() {
Project project = new Project();
project.setBaseDir(new File(config.getTomcatHome()+File.separator+"bin"));
Java javaTask = new Java();
javaTask.setProject(project);
javaTask.setDir(new java.io.File(config.getTomcatHome()+File.separator+"bin"));
javaTask.setTaskName("tomcat");
javaTask.setClassname("org.apache.catalina.startup.Bootstrap");
Path classpath = new Path(project);
classpath.createPathElement().setPath(config.getTomcatHome()+File.separator+"bin");
classpath.createPathElement().setPath(config.getTomcatHome()+File.separator+"bin"+File.separator+"bootstrap.jar");
classpath.createPathElement().setPath(config.getTomcatHome()+File.separator+"bin"+File.separator+"commons-logging-api.jar");
javaTask.setClasspath(classpath);
javaTask.createJvmarg().setValue("-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager");
javaTask.createJvmarg().setValue("-Djava.util.logging.config.file="+config.getTomcatHome()+File.separator+"conf"+File.separator+"logging.properties");
javaTask.createJvmarg().setValue("-Djava.endorsed.dirs="+config.getTomcatHome()+File.separator+"common"+File.separator+"endorsed");
javaTask.createJvmarg().setValue("-Dcatalina.base="+config.getTomcatHome());
javaTask.createJvmarg().setValue("-Dcatalina.home="+config.getTomcatHome());
javaTask.createJvmarg().setValue("-Djava.io.tmpdir="+config.getTomcatHome()+File.separator+"temp");
return javaTask;
}
public static void main(String[] args) throws InterruptedException {
// Quick test - should really be in a JUnit test
TomcatServerConfiguration config = new TomcatServerConfiguration();
config.setName("Engine");
config.setTomcatHome("c:\\apache-tomcat-5.5.26");
config.addJvmArg("-Xmx512m");
config.addJvmArg("-Dcom.sun.management.jmxremote");
config.addJvmArg("-Dcom.sun.management.jmxremote.port=9128");
config.addJvmArg("-Dcom.sun.management.jmxremote.ssl=false");
config.addJvmArg("-Dcom.sun.management.jmxremote.authenticate=false");
TomcatController tc = new TomcatController(config);
tc.start();
Thread.currentThread().sleep(20000);
tc.stop();
}
}
TomcatServerConfiguration
import java.util.ArrayList;
import java.util.List;
public class TomcatServerConfiguration {
private String name;
private String tomcatHome;
private List<String> jvmArgs = new ArrayList<String>();
public TomcatServerConfiguration() {
}
public String getName() {
return name;
}
public TomcatServerConfiguration setName(String name) {
this.name = name;
return this;
}
public String getTomcatHome() {
return tomcatHome;
}
public TomcatServerConfiguration setTomcatHome(String tomcatHome) {
this.tomcatHome = tomcatHome;
return this;
}
public List<String> getJvmArgs() {
return jvmArgs;
}
public void addJvmArg(String arg) {
jvmArgs.add(arg);
}
public void clearJvmArgs() {
jvmArgs.clear();
}
}
5 comments:
Jason, doesn't Tomcat have JMX controls these days? Couldn't you use a generic JMX client to start, stop & monitor Tomcat w/o having to fallback to the cmdline?
Hi Jason,
There is major problem with your approach.When you start/stop a tomcat instance using ant's 'javaTask' then you are not able to start/stop same tomcat by using it's native service.
Cause is 'bootstrap.jar' is locked by JVM.
I'd go with the JMX option too, add the following to your CATALINA_OPTS:
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8086 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
And point jconsole at it. In the MBean tab click on Catalina namespace and you can restart the engine or host or service or the individual contexts.
Yes, Tomcat has a built-in JMX MBeanServer (kind of redundant for JDK 1.5+ because now it has one too). I came across MBeans that could control the life-cycle of individual contexts but failed to find MBeans that could control the engine - might be worth another look.
probably a bit late but just came across this now! I've been looking for a way to restart tomcat programmatically as well. We have a problem where out tomcat instances lock up because we have to use a dodgy odbc driver for some of the databases we access.
There is an mbean Catalina:type=Engine that has two operations start and stop. that should do it i imagine!
Quite a handy little thing this JMX stuff. Just learning about it myself at the moment.
Post a Comment