All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.jef4.exec.SystemCommand Maven / Gradle / Ivy

Go to download

JEF is a Java API library meant to facilitate the lives of developers and integrators who have to build any kind of maintenance tasks on a server.

There is a newer version: 5.0.0-M1
Show newest version
/* Copyright 2010-2014 Norconex Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.norconex.jef4.exec;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

import com.norconex.commons.lang.io.IStreamListener;
import com.norconex.jef4.job.group.AsyncJobGroup;

/**
 * Represents a program to be executed by the underlying system
 * (on the "command line").  This class attempts to be system-independent,
 * which means given an executable path should be sufficient to run
 * programs on any systems (e.g. it handles prefixing an executable with OS
 * specific commands as well as preventing process hanging on some OS when
 * there is nowhere to display the output).  
 * 
 * @author Pascal Essiembre
 * @since 1.1
 */
@SuppressWarnings("nls")
public class SystemCommand {

    /** Logger. */
    private static final Logger LOG = LogManager.getLogger(SystemCommand.class);

    private static final String[] EMPTY_STRINGS = new String[] {};
    private static final String[] CMD_PREFIXES_WIN_LEGACY = 
    		new String[] { "command.com", "/C" };
    private static final String[] CMD_PREFIXES_WIN_CURRENT = 
			new String[] { "cmd.exe", "/C" };
    private static final IStreamListener[] EMPTY_LISTENERS =
    		new IStreamListener[] {};
    
    /** The command to run. */
    private final String[] command;
    /** The command description. */
    private String desc;
    /** Whether to inherit the working directory. */
    private final File workdir;

    /** Process STDERR listeners. */
    private final List errorListeners =
            Collections.synchronizedList(new ArrayList());
    /** Process STDOUT listeners. */
    private final List outputListeners =
            Collections.synchronizedList(new ArrayList());

    private Process process;
    
    /**
     * Creates a command for which the execution will be in the working
     * directory of the current process.  If more than one command values
     * are passed, the first element of the array
     * is the command and subsequent elements are arguments.
     * @param command the command to run
     */
    public SystemCommand(String... command) {
    	this(null, command);
    }
    
    /**
     * Creates a command. If more than one command values
     * are passed, the first element of the array
     * is the command and subsequent elements are arguments.
     * @param command the command to run
     * @param workdir specifies a working directory (default inherits
     *        the working directory of the current process.
     */
    public SystemCommand(File workdir, String... command) {
        super();
        this.command = command;
        this.workdir = workdir;
    }
    
    
    /**
     * Gets the command to be run.
     * @return the command
     */
    public String[] getCommand() {
        return ArrayUtils.clone(command);
    }

    /**
     * Gets the description for this command.
     * @return command description
     */
    public String getDescription() {
        return desc;
    }
    /**
     * Sets the description for this command.
     * @param description command description
     */
    public void setDescription(String description) {
        this.desc = description;
    }
    

    /**
     * Gets the command working directory.
     * @return command working directory.
     */
    public File getWorkdir() {
    	return workdir;
    }

    /**
     * Adds an error (STDERR) listener to this system command.
     * @param listener command error listener
     */
    public void addErrorListener(
            final IStreamListener listener) {
        synchronized (errorListeners) {
        	errorListeners.add(0, listener);
        }
    }
    /**
     * Removes an error (STDERR) listener.
     * @param listener command error listener
     */
    public void removeErrorListener(
            final IStreamListener listener) {
        synchronized (errorListeners) {
        	errorListeners.remove(listener);
        }
    }
    /**
     * Adds an output (STDOUT) listener to this system command.
     * @param listener command output listener
     */
    public void addOutputListener(
            final IStreamListener listener) {
        synchronized (outputListeners) {
        	outputListeners.add(0, listener);
        }
    }
    /**
     * Removes an output (STDOUT) listener.
     * @param listener command output listener
     */
    public void removeOutputListener(
            final IStreamListener listener) {
        synchronized (outputListeners) {
        	outputListeners.remove(listener);
        }
    }

    /**
     * Returns whether the command is currently running.
     * @return true if running
     */
    public boolean isRunning() {
    	if (process == null) {
    		return false;
    	}
    	try {
        	process.exitValue();
        	return false;
    	} catch (IllegalThreadStateException e) {
    		return true;
    	}
    }

    /**
     * Aborts the running command.  If the command is not currently running,
     * aborting it will have no effect.
     */
    public void abort() {
        if (process != null) {
            process.destroy();
        }
    }

    /**
     * Executes the given command and returns only when the underlying process
     * stopped running.  
     * @return process exit value
     * @throws InterruptedException problem executing command
     * @throws IOException problem executing command
     */
    public int execute() throws InterruptedException, IOException {
        return execute(false);
    }
    
    /**
     * Executes the given system command.  When run in the background,
     * this method will return quickly, with a status code of 0.
     * The status will not reflect the sub-process termination status.  
     * When NOT run in the background, this method waits and returns 
     * only when the underlying process stopped running.  
     * Alternatively, to run a command asynchronously, you can wrap it in 
     * its own thread (e.g. wrapping it in a {@link AsyncJobGroup}).
     * @param runInBackground true to runs the system command in 
     *         background.
     * @return process exit value
     * @throws InterruptedException problem executing command
     * @throws IOException problem executing command
     * @since 2.0
     */
    public int execute(boolean runInBackground) 
            throws InterruptedException, IOException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Executing command: " + toString());
        }
        if (isRunning()) {
        	throw new IllegalStateException("Command is already running: "
        			+ toString());
        }
        String[] prefixes = getOSCommandPrefixes();
        String[] fullCommand = ArrayUtils.addAll(prefixes, command);
        process = Runtime.getRuntime().exec(fullCommand, null, workdir);
        int exitValue = 0;
        if (runInBackground) {
            ExecUtils.watchProcessOutput(
                    process, 
                    outputListeners.toArray(EMPTY_LISTENERS),
                    errorListeners.toArray(EMPTY_LISTENERS));
            try {
                // Check in case the process terminated abruptly.
                exitValue = process.exitValue();
            } catch (IllegalThreadStateException e) {
                // Do nothing
            }
        } else {
            exitValue = ExecUtils.watchProcess(
                    process, 
                    outputListeners.toArray(EMPTY_LISTENERS),
                    errorListeners.toArray(EMPTY_LISTENERS));
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Command returned with exit value " + exitValue
            		+ ": " + toString());
        }
        if (exitValue != 0) {
            LOG.warn("Command returned exit value " + process.exitValue()
                    + ": " + toString());
        }
        process = null;
        return exitValue;
    }

    /**
     * Returns the command to be executed.
     */
    @Override
    public String toString() {
    	String cmd = "";
    	for (int i = 0; i < command.length; i++) {
			String element = command[i];
			cmd += element + " ";
		}
        return cmd.trim();
    }

    private String[] getOSCommandPrefixes() {
    	//TODO consider using "nice" on *nix systems.
    	if (SystemUtils.OS_NAME == null) {
    		return EMPTY_STRINGS;
    	}
    	if (SystemUtils.IS_OS_WINDOWS) {
    		if (SystemUtils.IS_OS_WINDOWS_95
    				|| SystemUtils.IS_OS_WINDOWS_98
    				|| SystemUtils.IS_OS_WINDOWS_ME) {
    			return CMD_PREFIXES_WIN_LEGACY;
    		}
            // NT, 2000, XP and up
			return CMD_PREFIXES_WIN_CURRENT;
    	}
    	return EMPTY_STRINGS;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy