This doc describes the design-in-progress for revamping the command-line execution of openejb.
Basic ideas:
- Commands can be added/removed (start, stop, test, validate, deploy)
- Adding/removing only requires adding/removing jars from the classpath
We can stuff properties files into jars at:
META-INF/org.openejb.cli/{name}
The propeties file will contain a main.class property, maybe an optional main.method property, and a set of description properties.
Here is an example of the start command:
It would be located at
META-INF/org.openejb.cli/start
main.class=org.openejb.server.Main
description.en=Starts the Remote Server
description.es=Ejecuta el Servidor Remoto
We would pull in all these files in the launcher's main method and parse them. If someone typed "openejb --help" then we would list the commands and descriptions.
Hiram wrote some code like this for activeio
/**
*
*/
package org.activeio;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;
public class FactoryFinder {
private final String path;
private final ConcurrentHashMap classMap = new ConcurrentHashMap();
public FactoryFinder(String path) {
this.path = path;
}
/**
* Creates a new instance of the given key
*
* @param key
* is the key to add to the path to find a text file
* containing the factory name
* @return a newly created instance
*/
public Object newInstance(String key) throws IllegalAccessException, InstantiationException, IOException,
ClassNotFoundException {
return newInstance(key, null);
}
public Object newInstance(String key, String propertyPrefix) throws IllegalAccessException,
InstantiationException, IOException, ClassNotFoundException {
if (propertyPrefix == null)
propertyPrefix = "";
Class clazz = (Class) classMap.get(propertyPrefix+key);
if( clazz == null ) {
clazz = newInstance(doFindFactoryProperies(key), propertyPrefix);
}
return clazz.newInstance();
}
private Class newInstance(Properties properties, String propertyPrefix) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, IOException {
String className = properties.getProperty(propertyPrefix + "class");
if (className == null) {
throw new IOException("Expected property is missing: " + propertyPrefix + "class");
}
Class clazz;
try {
clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
clazz = FactoryFinder.class.getClassLoader().loadClass(className);
}
return clazz;
}
private Properties doFindFactoryProperies(String key) throws IOException, ClassNotFoundException {
String uri = path + key;
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(uri);
if (in == null) {
in = FactoryFinder.class.getClassLoader().getResourceAsStream(uri);
if (in == null) {
throw new IOException("Could not find factory class for resource: " + uri);
}
}
BufferedInputStream reader = null;
try {
reader = new BufferedInputStream(in);
Properties properties = new Properties();
properties.load(reader);
return properties;
} finally {
try {
reader.close();
} catch (Exception e) {
}
}
}
}
If we used a class similar to that, we could get the commands like such:
FactoryFinder finder = new FactoryFinder("META-INF/org.openejb.cli/");
Properties props = finder.doFindFactoryProperies("start")
commands.put("start",props);
String local =
for each commands.entrySet() ... {
Map.Entry entry = commandEntries.next();
String command = entry.getKey();
Properties props = (Properties) entry.getValue();
String description = props.getProperty("description."+local, props.getProperty("description"));
System.out.print(" "+command+"\t"+description);
}
Properties props = (Properties)commands.get("start");
String mainClass = props.getProperty("main.class");
Class clazz = getClassLoader().loadClass(mainClass);
Method mainMethod = clazz.getMethod("main", new Class[]{String[].class});
mainMethod.invoke(args);
I took a different approach. Since we won't use this class to actually return a class loaded from the properties file, I made minor changes. I also made the CommandFinder.java capable of finding all possible command homes so that others wouldn't have to implement it themselves. Also, the CommandFinder will automatically set the openejb.home. The idea is that we may be able to get rid of all of the scripts checking for OPENEJB_HOME for us. This is the initial concept so please make changes or suggestions.
package org.openejb.cli;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class CommandFinder {
private String path;
private Map classMap = Collections.synchronizedMap(new HashMap());
public CommandFinder(String path) {
this.path = path;
}
public Properties doFindCommandProperies(String key) throws IOException {
String uri = path + key;
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(uri);
if (in == null) {
in = CommandFinder.class.getClassLoader().getResourceAsStream(uri);
if (in == null) {
throw new IOException("Could not find factory class for resource: " + uri);
}
}
BufferedInputStream reader = null;
try {
reader = new BufferedInputStream(in);
Properties properties = new Properties();
properties.load(reader);
URL propsURL = Thread.currentThread().getContextClassLoader().getResource(uri);
String propsString = propsURL.getFile();
URL jarURL;
File jarFile;
propsString = propsString.substring(0, propsString.indexOf("!"));
jarURL = new URL(propsString);
jarFile = new File(jarURL.getFile());
if (jarFile.getName().indexOf("openejb-core") > -1) {
File lib = jarFile.getParentFile();
File home = lib.getParentFile();
System.setProperty("openejb.home", home.getAbsolutePath());
}
return properties;
} finally {
try {
reader.close();
} catch (Exception e) {
}
}
}
public Enumeration doFindCommands() throws IOException {
return Thread.currentThread().getContextClassLoader().getResources(path);
}
}
The usage for this is the same as before but you would use the following approach to run instead of the OPENEJB_HOME/bin/openejb command:
java -jar OPENEJB_HOME/lib/openejb-core-<VERSION>.jar
Eventually, once David and I talk, we will wrap this in a script, like we do now. Right now, only the core commands are implemented:
- deploy
- help
- start
- stop
- validate
- Classpath - Will the command implementors be responsible for managing their classpath?
- Logging - Will we log errors in the CommandFinder.java or continue as-is outputting StackTrace and OpenEJB messages
- Handling non-core command - We should have an OPENEJB_HOME/lib/etc where all 3rd party commands, like tests, can house their jars. We need a standard location so the code can just work without the user having to add things to the classpath manually
- Wrapping in script - We will eventually wrap our executable jar in a script.