blob: bf924bfde094b2d141dcc623383b70b63ba636c7 [file] [log] [blame]
Title: Executables
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:
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
<DIV class="code panel" style="border-style: solid;border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><B>start</B></DIV><DIV class="codeContent panelContent">
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.
<a name="Executables-Gettingthepropertiesfiles"></a>
# Getting the properties files
Hiram wrote some code like this for activeio
<DIV class="code panel" style="border-style: solid;border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><B>FactoryFinder.java</B></DIV><DIV class="codeContent panelContent">
/**
*
*/
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;
// lets try the thread context class loader first
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);
}
}
// lets load the file
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:
<DIV class="code panel" style="border-style: solid;border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><B>Main.java</B></DIV><DIV class="codeContent panelContent">
FactoryFinder finder = new FactoryFinder("META-INF/org.openejb.cli/");
Properties props = finder.doFindFactoryProperies("start")
commands.put("start",props);
// we should try and load them all into properties instances and map them
in advance
//.. later to list help
String local = //get the i18n 2 character local (en, es, fr...)
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);
}
//.. later to execute a command
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); // obviously the "start" arg has been shaved off
first
<a name="Executables-Actualimplementation"></a>
# Actual implementation
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.
<DIV class="code panel" style="border-style: solid;border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><B>CommandFinder.java</B></DIV><DIV class="codeContent panelContent">
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;
// lets try the thread context class loader first
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);
}
}
// lets load the file
BufferedInputStream reader = null;
try {
reader = new BufferedInputStream(in);
Properties properties = new Properties();
properties.load(reader);
//All is well, set openejb.home
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);
}
}
<a name="Executables-CurrentImplementationUsage"></a>
# Current Implementation Usage
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:
<DIV class="code panel" style="border-style: solid;border-width: 1px;"><DIV class="codeHeader panelHeader" style="border-bottom-width: 1px;border-bottom-style: solid;"><B>Usage</B></DIV><DIV class="codeContent panelContent">
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
<a name="Executables-CurrentQuestionsBeforeIntegratingIntoMainstream"></a>
# Current Questions Before Integrating Into Mainstream
* 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.
<a name="Executables-Help!"></a>
# Help!
openejb --help
(list the commands and descriptions)
This would be the job of the main class of the launcher. It would find all
the commands in the system, and list their names and print their
"description" property.
openejb start --help
(list the start help text)
The main class of the launcher would do nothing with this. The start
command would need to grab it's help text and print it to the system.out.
Yes, this is extra work, but the various commands already support this.
Also, at some point we won't have the help text as is and will use
commons.cli to create the text.