Overview

Servers

Integrations

Community

Related Projects

Index

Feeds

 

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

Getting the properties files

Hiram wrote some code like this for activeio

FactoryFinder.java
/**
 * 
 */
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:

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

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.

CommandFinder.java
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);
    }
}

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:

Usage
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

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.

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.

   

Apache OpenEJB is an project of The Apache Software Foundation (ASF)
Site Powered by Atlassian Confluence .
[ edit ]