| 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. |