Embedding FOSS: ActiveMQ, Tomcat and Derby

Ever written a test-suite and wanted an ad-hoc app-server deployment (such as tomcat) that you could create (and use) on the fly, only to get rid of it once the test suite completed? Ever written applications and wanted to create an embedded database instance (such as derby) or message-queue (such as your private ActiveMQ instance) for internal use?

In this post, I present an approach to creating self-contained instances of some popular, free and open-source products. I also provide working code for a product that facilitates the dynamic creation of ActiveMQ, Tomcat and Derby instances for embedded and/or ad-hoc use.

Let me jump directly into the code.

The InstallableProduct interface

First, I define an interface named “InstallableProduct” that defines the life-cycle of such embedded and/or ad-hoc application instances:

public interface InstallableProduct {
    /**
     * Sets up (installs) the product in the given
     * <tt>installationDirectory</tt>.
     *
     * @param installationDirectory
     * @throws IOException
     *             if there is a problem setting up the product.
     */
    void setup() throws IOException;

    /**
     * Configures the product after it has been setup.
     *
     * @throws IOException
     *             if there is a problem configuring the product.
     */
    void configure() throws IOException;

    /**
     * Starts the product.
     *
     * @throws IOException
     * @throws InterruptedException
     */
    void startup(long millis) throws IOException, InterruptedException;

    /**
     * Stops the product.
     *
     * @throws IOException
     * @throws InterruptedException
     */
    void shutdown(long millis) throws IOException, InterruptedException;

    /**
     * Checks if the product is running.
     * @return
     * @throws IOException
     */
    boolean isRunning() throws IOException;

    /**
     * Uninstalls the product.
     * @throws IOException
     */
    void uninstall() throws IOException;
}

The following diagram shows the life-cycle of an InstalledProduct:

 

installable-product-lifecycle

The first step in the life-cycle of an InstallableProduct is setup, which creates, in a user-specified directory,  a set of files embodying the product. The created files represent a ready-to-run image of the product, and thus include all the startup and shutdown scripts, JAR files, configuration files, etc., that a stand-alone installation of the product needs.

The next step in the life-cycle of an InstallableProduct is configuration, when product-specific configuration parameters, such as port numbers, database names, etc. are written into the installation.

Next, the InstallableProduct instance can be started or stopped as many times as desired.

Finally, the InstallableProduct instance can be uninstalled, which simply removes the installation directory of the instance.

PortBasedInstallableProduct implementation

Since many InstalledProducts take the form of daemon-processes that provide services via one or more ports, I created an abstract subclass of InstallableProduct named PortBasedInstallableProduct:

/**
* Abstract class models products that run as daemon processes listening on a
* known port to provide services.
*
* @author sdasgupta
*
*/
public abstract class PortBasedInstallableProduct implements InstallableProduct {
    private static final Logger sLog = Logger
            .getLogger(PortBasedInstallableProduct.class);
    static {
        // If there are no appenders, use the basic configurator to add one.
        if (!sLog.getAllAppenders().hasMoreElements()) {
            BasicConfigurator.configure();
        }
    }
    private final int listeningPort;

    public PortBasedInstallableProduct(int listeningPort) {
        super();
        this.listeningPort = listeningPort;
    }

    public boolean isRunning() throws IOException {
        return NetUtils.isListening(
                getLocalHost(), listeningPort);
    }

    public final void shutdown(long millis) throws IOException,
            InterruptedException {
        sLog.info("Shutting down product.";
        doShutdown();
        sLog
                .info("Shutdown signal sent : waiting for port to stop listening - "
                        + listeningPort);
        NetUtils.waitToDisconnect(getLocalHost(),
                listeningPort, millis);
        sLog.info("Product has shut down.";
    }

    protected abstract void doShutdown() throws IOException,
            InterruptedException;

    public final void startup(long millis) throws IOException,
            InterruptedException {
        sLog.info("Starting product.";
        if (isRunning()) {
            throw new IllegalStateException(
                    "Already running: listening on port: " + listeningPort);
        }
        doStartup();
        sLog
                .info("Product has been started up : waiting for port to begin listening - "
                        + listeningPort);
        NetUtils.waitToConnect(getLocalHost(),
                listeningPort, millis);
        sLog.info("Product startup complete.";
    }

    private String getLocalHost() throws UnknownHostException {
        return "localhost";
    }

    protected abstract void doStartup() throws IOException,
            InterruptedException;

    protected int getListeningPort() {
        return listeningPort;
    }
}

Note that PortBasedInstallableProduct does not implement “configure”, and requires its concrete sub-classes to imp;lement two methods, namely, “doStart” and “doStop”.

Next, I present source code for some concrete InstallableProducts.

Embeddable Tomcat

The EmbeddableTomcat class creates a tomcat instance in a given installation directory, and providing HTTP service over a given port.  Note how the “configure” method sets up port numbers in the “conf/server.xml” file of the instance:

public void configure() throws IOException {
    try {
        File serverXml = new File(installationDirectory, "conf/server.xml";
        Document doc = XmlUtils.loadDocument(serverXml);

        int index = 1;
        int shutdownPort = getListeningPort() + index++;
        int redirectPort = getListeningPort() + index++;
        int ajpPort = getListeningPort() + index++;

        // Replace the shutdown port
        Element serverElement = (Element) XPath.selectSingleNode(doc
                .getRootElement(), "/Server[@shutdown='SHUTDOWN']";
        serverElement.setAttribute("port", "" + shutdownPort);

        // Replace the listening and redirect ports.
        Element connectorElement = (Element) XPath
                .selectSingleNode(doc.getRootElement(),
                        "/Server/Service[@name='Catalina']//Connector[@executor='tomcatThreadPool']";
        connectorElement.setAttribute("port", "" + getListeningPort());
        connectorElement.setAttribute("redirectPort", "" + redirectPort);

        // Replace the AJP port.
        Element ajpElement = (Element) XPath
                .selectSingleNode(doc.getRootElement(),
                        "/Server/Service[@name='Catalina']//Connector[@protocol='AJP/1.3']";
        ajpElement.setAttribute("port", "" + ajpPort);
        ajpElement.setAttribute("redirectPort", "" + redirectPort);

        // Save the updated serverXml.
        FileUtils.saveTextFile(serverXml, XmlUtils.toString(doc));
    } catch (JDOMException exc) {
        throw new IOException(StrUtils.toString(exc));
    }
}

and how the “doStart” and “doStop” methods execute “bin/catalina start” and “bin/shutdown”, respectively.

Embeddable Derby

Creates an embeddable Derby instance in a specified installation directory, listening on a given port and creating a given database. I do not reproduce snippets of the source code here since the “setup” and “configure” methods are somewhat involved (and, I am afraid, over-complicated).

Embeddable ActiveMQ

Creates an embeddable ActiveMQ instance in a specified installation directory and providing service via a specified port.

Download

You can download a JAR file containing the InstallableProduct class and all its dependencies from here, and a zip file containing the source code from here.. Look at the com.subhajit.embeddable.packaging.Main class once you download the source files to see how to use these classes:

public class Main {
    public static void main(String[] args) {
        int status = 0;
        try {
            System.out.println("Contents: "
                    + EmbeddableTomcat.class.getSimpleName() + IConstants.COMMA
                    + EmbeddedActiveMQ.class.getName() + IConstants.COMMA
                    + EmbeddedDerby.class.getName());
            File tmpDir = new File("tmp";
            try {
                FileUtils.mkdirs(tmpDir);
                final File installationDir = new File(tmpDir, "temp";
                EmbeddableTomcat tomcat = new EmbeddableTomcat(installationDir,
                        20000);
                tomcat.setup();
                tomcat.configure();
                tomcat.startup(10000);
                tomcat.shutdown(10000);
                tomcat.uninstall();

                EmbeddedDerby derby = new EmbeddedDerby(installationDir,
                        "test", 20000);
                derby.setup();
                derby.configure();
                derby.startup(10000);
                derby.shutdown(10000);
                derby.uninstall();

                EmbeddedActiveMQ activeMQ = new EmbeddedActiveMQ(
                        installationDir, 20000);
                activeMQ.setup();
                activeMQ.configure();
                activeMQ.startup(10000);
                activeMQ.shutdown(10000);
                activeMQ.uninstall();
            } finally {
                if (tmpDir != null && tmpDir.exists()) {
                    FileUtils.deleteDirectoryAndContents(tmpDir);
                }
            }
        } catch (Throwable exc) {
            status = 1;
            exc.printStackTrace();
        } finally {
            System.exit(status);
        }
    }
}

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this entry.
Comments
  • No comments exist for this entry.
Leave a comment

Submitted comments will be subject to moderation before being displayed.

 Enter the above security code (required)

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.