org.daisy.pipeline.execution.rmi.PoolableRMIPipelineInstanceFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.daisy.pipeline.execution.rmi.PoolableRMIPipelineInstanceFactory.java

Source

/*
 * Daisy Pipeline (C) 2005-2008 Daisy Consortium
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.daisy.pipeline.execution.rmi;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.rmi.NotBoundException;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeoutException;

import org.apache.commons.pool.BasePoolableObjectFactory;
import org.daisy.pipeline.core.PipelineCore;
import org.daisy.pipeline.rmi.RMIPipelineApp;
import org.daisy.pipeline.rmi.RMIPipelineInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A factory of {@link RMIPipelineInstanceWrapper} to be used with pools from
 * the Commons Pool library.
 * 
 * <p>
 * This factory is configured with the path to the Pipeline home directory, the
 * RMI registry used by the RMI Pipeline instances, and optionally a timeout
 * value to stop waiting for a newly created instance to appear in the registry.
 * </p>
 * <p>
 * The remote Pipeline instances will be based on the {@link RMIPipelineApp}
 * application in the confiured pipeline directory. The java command used to
 * launch these instances can be configured with the file
 * <code>$PIPELINE_DIR/rmi/vmargs.conf</code>, where each line wil be added as a
 * parameter of the command.
 * </p>
 * <p>
 * It uses an internal static holder to initialize an RMI registry on the port
 * 1099 of the localhost.
 * </p>
 * 
 * @author Romain Deltour
 * 
 */
public class PoolableRMIPipelineInstanceFactory extends BasePoolableObjectFactory {

    private final Logger logger = LoggerFactory.getLogger(PoolableRMIPipelineInstanceFactory.class);

    private String javaPath = "java";
    private int timeout = 10000;
    private File pipelineDir;
    private Registry rmiRegistry;

    /**
     * Sets the path to the JVM launcher used to launch the remote Pipeline
     * instances. THe default value is <code>java</code>.
     * 
     * @param javaPath
     *            the path to the JVM launcher.
     */
    public void setJavaPath(String javaPath) {
        File file = new File(javaPath);
        if (!file.exists()) {
            this.javaPath = javaPath;
        } else {
            logger.info("Invalid java command: {}", javaPath);
        }
    }

    /**
     * Sets the path to the pipeline installation directory used to launch the
     * remote Pipeline instances.
     * 
     * @param pipelinePath
     *            the path to the pipeline installation directory
     */
    public void setPipelinePath(String pipelinePath) {
        this.pipelineDir = new File(pipelinePath);
    }

    /**
     * Sets the RMI registry used to bind the remote pipeline instances
     * 
     * @param rmiRegistry
     *            an RMI registry
     */
    public void setRmiRegistry(Registry rmiRegistry) {
        this.rmiRegistry = rmiRegistry;
    }

    /**
     * Sets the timeout value (in milliseconds) to stop waiting for a newly
     * created instance to appear in the registry
     * 
     * @param timeout
     *            the creation timeout value (in milliseconds)
     */
    public void setTimeout(String timeout) {
        try {
            this.timeout = Integer.valueOf(timeout);
        } catch (NumberFormatException e) {
            logger.info("Invalid timeout value: {}", timeout);
        }
    }

    /**
     * Checks that the pool has been correctly configured.
     */
    public void init() {
        if (!pipelineDir.exists()) {
            throw new IllegalStateException("Pipeline dir does not exist: " + pipelineDir.getPath());
        }
        if (!pipelineDir.isDirectory()) {
            throw new IllegalStateException("Pipeline dir is not a directory: " + pipelineDir.getPath());
        }
        if (!PipelineCore.testHomeDirectory(pipelineDir)) {
            throw new IllegalStateException(
                    "Pipeline dir does not point to a Pipeline installation: " + pipelineDir.getPath());
        }
        if (rmiRegistry == null) {
            throw new IllegalStateException("RMI registry is null");
        }
    }

    private Process launchNewPipelineInstance(final String id) throws IOException {
        List<String> cmd = new ArrayList<String>();
        cmd.add(javaPath);

        // Add VM arguments
        BufferedReader br = null;
        try {
            File argFile = new File(pipelineDir, "rmi/vmargs.conf");
            if (argFile.exists()) {
                br = new BufferedReader(new FileReader(argFile));
                String line;
                while ((line = br.readLine()) != null) {
                    cmd.add(line);
                }
            }
        } catch (Exception e) {
            logger.warn("Couldn't read VM arguments", e);
        } finally {
            if (br != null) {
                br.close();
            }
        }

        // Add class path
        cmd.add("-cp");
        cmd.add(buildClassPath());

        // Add application class and arguments
        cmd.add(RMIPipelineApp.class.getName());
        cmd.add(id);
        String[] cmdarray = (String[]) cmd.toArray(new String[cmd.size()]);

        if (logger.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder();
            for (String item : cmdarray) {
                sb.append(item).append(' ');
            }
            logger.debug("Launching {} with cmd \"{}\"", id, sb.toString());
        }
        final Process process = Runtime.getRuntime().exec(cmdarray, null, pipelineDir);

        // Register a shutdown hook to terminate live RMI Pipeline
        // instances when the JVM quits.
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                process.destroy();
            }
        });
        return process;
    }

    private String buildClassPath() {
        StringBuilder sb = new StringBuilder();
        sb.append(".").append(File.pathSeparatorChar);
        if (new File(pipelineDir, "bin").exists()) {
            sb.append("bin").append(File.separatorChar).append(File.pathSeparatorChar);
        } else {
            sb.append("pipeline.jar").append(File.pathSeparatorChar);
        }
        for (File file : new File(pipelineDir, "lib").listFiles(new FileFilter() {
            public boolean accept(File pathname) {
                return pathname.isFile() && pathname.getName().endsWith(".jar");
            }
        })) {
            sb.append(pipelineDir.toURI().relativize(file.toURI()).getPath()).append(File.pathSeparatorChar);
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

    /**
     * Launch a new RMI Pipeline instance from the launcher script, and wait for
     * this instance to be registered in the RMI Registry to return it.
     * 
     * @throws TimeoutException
     *             if the RMI Pipeline instance was not registered before a
     *             fixed 5000ms timeout.
     */
    public Object makeObject() throws Exception {
        String uuid = UUID.randomUUID().toString();
        Process process = launchNewPipelineInstance(uuid);
        long starttime = System.currentTimeMillis();
        long endtime = starttime + timeout;
        RMIPipelineInstance pipeline = null;
        while (pipeline == null && System.currentTimeMillis() <= endtime) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
            try {
                pipeline = (RMIPipelineInstance) rmiRegistry.lookup(uuid);
            } catch (NotBoundException e) {
            }
        }
        if (pipeline == null) {
            throw new TimeoutException("Couldn't load the Pipeline instance");
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Launched instance {}", uuid);
        }
        return new RMIPipelineInstanceWrapper(pipeline, process);
    }

    /**
     * Destroys the RMI Pipeline instance by calling
     * {@link RMIPipelineInstanceWrapper#shutdown()}.
     */
    @Override
    public void destroyObject(Object obj) throws Exception {
        ((RMIPipelineInstanceWrapper) obj).shutdown();
    }

    /**
     * Validates the RMI Pipeline instance by calling
     * {@link RMIPipelineInstanceWrapper#isReady()}.
     */
    @Override
    public boolean validateObject(Object obj) {
        try {
            return ((RMIPipelineInstanceWrapper) obj).isReady();
        } catch (Exception e) {
            logger.warn("invalid instance");
            return false;
        }
    }
}