com.panet.imeta.job.entries.shell.JobEntryShell.java Source code

Java tutorial

Introduction

Here is the source code for com.panet.imeta.job.entries.shell.JobEntryShell.java

Source

/* Copyright (c) 2007 Pentaho Corporation.  All rights reserved. 
 * This software was developed by Pentaho Corporation and is provided under the terms 
 * of the GNU Lesser General Public License, Version 2.1. You may not use 
 * this file except in compliance with the license. If you need a copy of the license, 
 * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Pentaho 
 * Data Integration.  The Initial Developer is Pentaho Corporation.
 *
 * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
 * the license for the specific language governing your rights and limitations.*/

package com.panet.imeta.job.entries.shell;

import static com.panet.imeta.job.entry.validator.AbstractFileValidator.putVariableSpace;
import static com.panet.imeta.job.entry.validator.AndValidator.putValidators;
import static com.panet.imeta.job.entry.validator.JobEntryValidatorUtils.andValidator;
import static com.panet.imeta.job.entry.validator.JobEntryValidatorUtils.fileExistsValidator;
import static com.panet.imeta.job.entry.validator.JobEntryValidatorUtils.notBlankValidator;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.vfs.FileObject;
import org.w3c.dom.Node;

import com.panet.imeta.cluster.SlaveServer;
import com.panet.imeta.core.CheckResultInterface;
import com.panet.imeta.core.Const;
import com.panet.imeta.core.Result;
import com.panet.imeta.core.RowMetaAndData;
import com.panet.imeta.core.database.DatabaseMeta;
import com.panet.imeta.core.exception.KettleDatabaseException;
import com.panet.imeta.core.exception.KettleException;
import com.panet.imeta.core.exception.KettleXMLException;
import com.panet.imeta.core.logging.Log4jFileAppender;
import com.panet.imeta.core.logging.LogWriter;
import com.panet.imeta.core.util.StreamLogger;
import com.panet.imeta.core.vfs.KettleVFS;
import com.panet.imeta.core.xml.XMLHandler;
import com.panet.imeta.job.Job;
import com.panet.imeta.job.JobEntryType;
import com.panet.imeta.job.JobMeta;
import com.panet.imeta.job.entry.JobEntryBase;
import com.panet.imeta.job.entry.JobEntryInterface;
import com.panet.imeta.job.entry.validator.ValidatorContext;
import com.panet.imeta.repository.Repository;
import com.panet.imeta.resource.ResourceEntry;
import com.panet.imeta.resource.ResourceReference;
import com.panet.imeta.resource.ResourceEntry.ResourceType;
import com.panet.imeta.shared.SharedObjectInterface;

/**
 * Shell type of Job Entry. You can define shell scripts to be executed in a
 * Job.
 * 
 * @author Matt
 * @since 01-10-2003, rewritten on 18-06-2004
 */
public class JobEntryShell extends JobEntryBase implements Cloneable, JobEntryInterface {

    public static final String STEP_ATTRIBUTE_FILE_NAME = "file_name ";
    public static final String STEP_ATTRIBUTE_WORK_DIRECTORY = "work_directory";
    public static final String STEP_ATTRIBUTE_ARG_FROM_PREVIOUS = "arg_from_previous ";
    public static final String STEP_ATTRIBUTE_EXEC_PER_ROW = "exec_per_row";
    public static final String STEP_ATTRIBUTE_SET_LOGFILE = "set_logfile ";
    public static final String STEP_ATTRIBUTE_SET_APPEND_LOGFILE = "set_append_logfile";
    public static final String STEP_ATTRIBUTE_ADD_DATE = "add_date";
    public static final String STEP_ATTRIBUTE_ADD_TIME = "add_time";
    public static final String STEP_ATTRIBUTE_LOGFILE = "logfile ";
    public static final String STEP_ATTRIBUTE_LOGEXT = "logext";
    public static final String STEP_ATTRIBUTE_LOGLEVEL = "loglevel";
    public static final String STEP_ATTRIBUTE_INSERTSCRIPT = "insertscript";
    public static final String STEP_ATTRIBUTE_SCRIPT = "script";

    private String filename;

    private String workDirectory;

    public String arguments[];

    public boolean argFromPrevious;

    public boolean setLogfile;

    public String logfile, logext;

    public boolean addDate, addTime;

    public int loglevel;

    public boolean execPerRow;

    public boolean setAppendLogfile;

    public boolean insertScript;

    public String script;

    public JobEntryShell(String name) {
        super(name, "");
        setJobEntryType(JobEntryType.SHELL);
    }

    public JobEntryShell() {
        this("");
        clear();
    }

    public JobEntryShell(JobEntryBase jeb) {
        super(jeb);
        setJobEntryType(JobEntryType.SHELL);
    }

    public Object clone() {
        JobEntryShell je = (JobEntryShell) super.clone();
        return je;
    }

    public String getXML() {
        StringBuffer retval = new StringBuffer(300);

        retval.append(super.getXML());

        retval.append("      ").append(XMLHandler.addTagValue("filename", filename));
        retval.append("      ").append(XMLHandler.addTagValue("work_directory", workDirectory));
        retval.append("      ").append(XMLHandler.addTagValue("arg_from_previous", argFromPrevious));
        retval.append("      ").append(XMLHandler.addTagValue("exec_per_row", execPerRow));
        retval.append("      ").append(XMLHandler.addTagValue("set_logfile", setLogfile));
        retval.append("      ").append(XMLHandler.addTagValue("logfile", logfile));
        retval.append("      ").append(XMLHandler.addTagValue("set_append_logfile", setAppendLogfile));
        retval.append("      ").append(XMLHandler.addTagValue("logext", logext));
        retval.append("      ").append(XMLHandler.addTagValue("add_date", addDate));
        retval.append("      ").append(XMLHandler.addTagValue("add_time", addTime));
        retval.append("      ").append(XMLHandler.addTagValue("insertScript", insertScript));
        retval.append("      ").append(XMLHandler.addTagValue("script", script));

        retval.append("      ").append(XMLHandler.addTagValue("loglevel", LogWriter.getLogLevelDesc(loglevel)));

        if (arguments != null)
            for (int i = 0; i < arguments.length; i++) {
                // THIS IS A VERY BAD WAY OF READING/SAVING AS IT MAKES
                // THE XML "DUBIOUS". DON'T REUSE IT. (Sven B)
                retval.append("      ").append(XMLHandler.addTagValue("argument" + i, arguments[i]));
            }

        return retval.toString();
    }

    public void loadXML(Node entrynode, List<DatabaseMeta> databases, List<SlaveServer> slaveServers,
            Repository rep) throws KettleXMLException {
        try {
            super.loadXML(entrynode, databases, slaveServers);
            setFileName(XMLHandler.getTagValue(entrynode, "filename"));
            setWorkDirectory(XMLHandler.getTagValue(entrynode, "work_directory"));
            argFromPrevious = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "arg_from_previous"));
            execPerRow = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "exec_per_row"));
            setLogfile = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "set_logfile"));
            setAppendLogfile = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "set_append_logfile"));
            addDate = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "add_date"));
            addTime = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "add_time"));
            logfile = XMLHandler.getTagValue(entrynode, "logfile");
            logext = XMLHandler.getTagValue(entrynode, "logext");
            loglevel = LogWriter.getLogLevel(XMLHandler.getTagValue(entrynode, "loglevel"));
            insertScript = "Y".equalsIgnoreCase(XMLHandler.getTagValue(entrynode, "insertScript"));

            script = XMLHandler.getTagValue(entrynode, "script");

            // How many arguments?
            int argnr = 0;
            while (XMLHandler.getTagValue(entrynode, "argument" + argnr) != null)
                argnr++;
            arguments = new String[argnr];

            // Read them all...
            // THIS IS A VERY BAD WAY OF READING/SAVING AS IT MAKES
            // THE XML "DUBIOUS". DON'T REUSE IT.
            for (int a = 0; a < argnr; a++)
                arguments[a] = XMLHandler.getTagValue(entrynode, "argument" + a);
        } catch (KettleException e) {
            throw new KettleXMLException("Unable to load job entry of type 'shell' from XML node", e);
        }
    }

    public void setInfo(Map<String, String[]> p, String id, List<? extends SharedObjectInterface> databases) {

        setFileName(JobEntryBase.parameterToString(p.get(id + ".filename")));
        setWorkDirectory(JobEntryBase.parameterToString(p.get(id + ".workDirectory")));
        argFromPrevious = JobEntryBase.parameterToBoolean(p.get(id + ".argFromPrevious"));
        execPerRow = JobEntryBase.parameterToBoolean(p.get(id + ".execPerRow"));
        setLogfile = JobEntryBase.parameterToBoolean(p.get(id + ".setLogfile"));
        setAppendLogfile = JobEntryBase.parameterToBoolean(p.get(id + ".setAppendLogfile"));
        addDate = JobEntryBase.parameterToBoolean(p.get(id + ".addDate"));
        addTime = JobEntryBase.parameterToBoolean(p.get(id + ".addTime"));
        logfile = JobEntryBase.parameterToString(p.get(id + ".logfile"));
        logext = JobEntryBase.parameterToString(p.get(id + ".logext"));
        loglevel = LogWriter.getLogLevel(JobEntryBase.parameterToString(p.get(id + ".loglevel")));
        insertScript = JobEntryBase.parameterToBoolean(p.get(id + ".insertScript"));
        script = JobEntryBase.parameterToString(p.get(id + ".script"));

        String[] arguments = p.get(id + "_words.arguments");

        this.arguments = arguments;

    }

    // Load the jobentry from repository
    public void loadRep(Repository rep, long id_jobentry, List<DatabaseMeta> databases,
            List<SlaveServer> slaveServers) throws KettleException {
        try {
            super.loadRep(rep, id_jobentry, databases, slaveServers);

            setFileName(rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_FILE_NAME));
            setWorkDirectory(rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_WORK_DIRECTORY));

            argFromPrevious = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_ARG_FROM_PREVIOUS);
            execPerRow = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_EXEC_PER_ROW);
            setLogfile = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_SET_LOGFILE);
            setAppendLogfile = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_SET_APPEND_LOGFILE);
            addDate = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_ADD_DATE);
            addTime = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_ADD_TIME);
            logfile = rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_LOGFILE);
            logext = rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_LOGEXT);
            loglevel = LogWriter.getLogLevel(rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_LOGLEVEL));
            insertScript = rep.getJobEntryAttributeBoolean(id_jobentry, STEP_ATTRIBUTE_INSERTSCRIPT);
            script = rep.getJobEntryAttributeString(id_jobentry, STEP_ATTRIBUTE_SCRIPT);

            // How many arguments?
            int argnr = rep.countNrJobEntryAttributes(id_jobentry, "argument");
            arguments = new String[argnr];

            // Read them all...
            for (int a = 0; a < argnr; a++) {
                arguments[a] = rep.getJobEntryAttributeString(id_jobentry, a, "argument");
            }
        } catch (KettleDatabaseException dbe) {
            throw new KettleException(
                    "Unable to load job entry of type 'shell' from the repository with id_jobentry=" + id_jobentry,
                    dbe);
        }
    }

    // Save the attributes of this job entry
    //
    public void saveRep(Repository rep, long id_job) throws KettleException {
        try {
            super.saveRep(rep, id_job);

            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_FILE_NAME, filename);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_WORK_DIRECTORY, workDirectory);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_ARG_FROM_PREVIOUS, argFromPrevious);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_EXEC_PER_ROW, execPerRow);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_SET_LOGFILE, setLogfile);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_SET_APPEND_LOGFILE, setAppendLogfile);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_ADD_DATE, addDate);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_ADD_TIME, addTime);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_LOGFILE, logfile);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_LOGEXT, logext);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_LOGLEVEL,
                    LogWriter.getLogLevelDesc(loglevel));
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_INSERTSCRIPT, insertScript);
            rep.saveJobEntryAttribute(id_job, getID(), STEP_ATTRIBUTE_SCRIPT, script);

            // save the arguments...
            if (arguments != null) {
                for (int i = 0; i < arguments.length; i++) {
                    rep.saveJobEntryAttribute(id_job, getID(), i, "argument", arguments[i]);
                }
            }
        } catch (KettleDatabaseException dbe) {
            throw new KettleException("Unable to save job entry of type 'shell' to the repository", dbe);
        }
    }

    public void clear() {
        super.clear();

        filename = null;
        workDirectory = null;
        arguments = null;
        argFromPrevious = false;
        addDate = false;
        addTime = false;
        logfile = null;
        logext = null;
        setLogfile = false;
        execPerRow = false;
        setAppendLogfile = false;
        insertScript = false;
        script = null;
    }

    public void setFileName(String n) {
        filename = n;
    }

    public String getFilename() {
        return filename;
    }

    public String getRealFilename() {
        return environmentSubstitute(getFilename());
    }

    public void setWorkDirectory(String n) {
        workDirectory = n;
    }

    public String getWorkDirectory() {
        return workDirectory;
    }

    public void setScript(String scriptin) {
        script = scriptin;
    }

    public String getScript() {
        return script;
    }

    public String getLogFilename() {
        String retval = "";
        if (setLogfile) {
            retval += logfile;
            Calendar cal = Calendar.getInstance();
            if (addDate) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
                retval += "_" + sdf.format(cal.getTime());
            }
            if (addTime) {
                SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
                retval += "_" + sdf.format(cal.getTime());
            }
            if (logext != null && logext.length() > 0) {
                retval += "." + logext;
            }
        }
        return retval;
    }

    public Result execute(Result result, int nr, Repository rep, Job parentJob) throws KettleException {
        LogWriter log = LogWriter.getInstance();

        Log4jFileAppender appender = null;
        int backupLogLevel = log.getLogLevel();
        if (setLogfile) {
            try {
                appender = LogWriter.createFileAppender(environmentSubstitute(getLogFilename()), true,
                        setAppendLogfile);
            } catch (KettleException e) {
                log.logError(toString(), Messages.getString("JobEntryShell.Error.UnableopenAppenderFile",
                        getLogFilename(), e.toString()));
                log.logError(toString(), Const.getStackTracker(e));
                result.setNrErrors(1);
                result.setResult(false);
                return result;
            }
            log.addAppender(appender);
            log.setLogLevel(loglevel);
        }

        result.setEntryNr(nr);

        // "Translate" the arguments for later
        String substArgs[] = null;
        if (arguments != null) {
            substArgs = new String[arguments.length];
            for (int idx = 0; idx < arguments.length; idx++) {
                substArgs[idx] = environmentSubstitute(arguments[idx]);
            }
        }

        int iteration = 0;
        String args[] = substArgs;
        RowMetaAndData resultRow = null;
        boolean first = true;
        List<RowMetaAndData> rows = result.getRows();

        if (log.isDetailed())
            log.logDetailed(toString(), Messages.getString("JobEntryShell.Log.FoundPreviousRows",
                    "" + (rows != null ? rows.size() : 0)));

        while ((first && !execPerRow)
                || (execPerRow && rows != null && iteration < rows.size() && result.getNrErrors() == 0)) {
            first = false;
            if (rows != null && execPerRow) {
                resultRow = (RowMetaAndData) rows.get(iteration);
            } else {
                resultRow = null;
            }

            List<RowMetaAndData> cmdRows = null;

            if (execPerRow) // Execute for each input row
            {
                if (argFromPrevious) // Copy the input row to the (command
                                     // line) arguments
                {
                    if (resultRow != null) {
                        args = new String[resultRow.size()];
                        for (int i = 0; i < resultRow.size(); i++) {
                            args[i] = resultRow.getString(i, null);
                        }
                    }
                } else {
                    // Just pass a single row
                    List<RowMetaAndData> newList = new ArrayList<RowMetaAndData>();
                    newList.add(resultRow);
                    cmdRows = newList;
                }
            } else {
                if (argFromPrevious) {
                    // Only put the first Row on the arguments
                    args = null;
                    if (resultRow != null) {
                        args = new String[resultRow.size()];
                        for (int i = 0; i < resultRow.size(); i++) {
                            args[i] = resultRow.getString(i, null);
                        }
                    } else {
                        cmdRows = rows;
                    }
                } else {
                    // Keep it as it was...
                    cmdRows = rows;
                }
            }

            executeShell(result, cmdRows, args);

            iteration++;
        }

        if (setLogfile) {
            if (appender != null) {
                log.removeAppender(appender);
                appender.close();
            }
            log.setLogLevel(backupLogLevel);

        }

        return result;
    }

    private void executeShell(Result result, List<RowMetaAndData> cmdRows, String[] args) {
        LogWriter log = LogWriter.getInstance();
        FileObject fileObject = null;
        String realScript = null;
        FileObject tempFile = null;

        try {
            // What's the exact command?
            String base[] = null;
            List<String> cmds = new ArrayList<String>();

            if (log.isBasic())
                log.logBasic(toString(), Messages.getString("JobShell.RunningOn", Const.getOS()));

            if (insertScript) {
                realScript = environmentSubstitute(script);
            } else {
                String realFilename = environmentSubstitute(getFilename());
                fileObject = KettleVFS.getFileObject(realFilename);
            }

            if (Const.getOS().equals("Windows 95")) {
                base = new String[] { "command.com", "/C" };
            } else if (Const.getOS().startsWith("Windows")) {
                base = new String[] { "cmd.exe", "/C" };
            } else {
                if (!insertScript) {
                    // Just set the command to the script we need to execute...
                    //
                    base = new String[] { KettleVFS.getFilename(fileObject) };
                } else {
                    // Create a unique new temporary filename in the working directory, put the script in there
                    // Set the permissions to execute and then run it...
                    //
                    try {
                        tempFile = KettleVFS.createTempFile("kettle", "shell", workDirectory);
                        tempFile.createFile();
                        OutputStream outputStream = tempFile.getContent().getOutputStream();
                        outputStream.write(realScript.getBytes());
                        outputStream.close();
                        String tempFilename = KettleVFS.getFilename(tempFile);
                        // Now we have to make this file executable...
                        // On Unix-like systems this is done using the command "/bin/chmod +x filename"
                        //
                        ProcessBuilder procBuilder = new ProcessBuilder("chmod", "+x", tempFilename);
                        Process proc = procBuilder.start();
                        // Eat/log stderr/stdout all messages in a different thread...
                        StreamLogger errorLogger = new StreamLogger(proc.getErrorStream(),
                                toString() + " (stderr)");
                        StreamLogger outputLogger = new StreamLogger(proc.getInputStream(),
                                toString() + " (stdout)");
                        new Thread(errorLogger).start();
                        new Thread(outputLogger).start();
                        proc.waitFor();

                        // Now set this filename as the base command...
                        //
                        base = new String[] { tempFilename };
                    } catch (Exception e) {
                        throw new Exception("Unable to create temporary file to execute script", e);
                    }
                }
            }

            // Construct the arguments...
            if (argFromPrevious && cmdRows != null) {
                // Add the base command...
                for (int i = 0; i < base.length; i++)
                    cmds.add(base[i]);

                if (Const.getOS().equals("Windows 95") || Const.getOS().startsWith("Windows")) {
                    // for windows all arguments including the command itself
                    // need to be
                    // included in 1 argument to cmd/command.

                    StringBuffer cmdline = new StringBuffer(300);

                    cmdline.append('"');
                    if (insertScript)
                        cmdline.append(realScript);
                    else
                        cmdline.append(optionallyQuoteField(KettleVFS.getFilename(fileObject), "\""));
                    // Add the arguments from previous results...
                    for (int i = 0; i < cmdRows.size(); i++) // Normally just
                    // one row, but
                    // once in a
                    // while to
                    // remain
                    // compatible we
                    // have
                    // multiple.
                    {
                        RowMetaAndData r = (RowMetaAndData) cmdRows.get(i);
                        for (int j = 0; j < r.size(); j++) {
                            cmdline.append(' ');
                            cmdline.append(optionallyQuoteField(r.getString(j, null), "\""));
                        }
                    }
                    cmdline.append('"');
                    cmds.add(cmdline.toString());
                } else {
                    // Add the arguments from previous results...
                    for (int i = 0; i < cmdRows.size(); i++) // Normally just
                    // one row, but
                    // once in a
                    // while to
                    // remain
                    // compatible we
                    // have
                    // multiple.
                    {
                        RowMetaAndData r = (RowMetaAndData) cmdRows.get(i);
                        for (int j = 0; j < r.size(); j++) {
                            cmds.add(optionallyQuoteField(r.getString(j, null), "\""));
                        }
                    }
                }
            } else if (args != null) {
                // Add the base command...
                for (int i = 0; i < base.length; i++)
                    cmds.add(base[i]);

                if (Const.getOS().equals("Windows 95") || Const.getOS().startsWith("Windows")) {
                    // for windows all arguments including the command itself
                    // need to be
                    // included in 1 argument to cmd/command.

                    StringBuffer cmdline = new StringBuffer(300);

                    cmdline.append('"');
                    if (insertScript)
                        cmdline.append(realScript);
                    else
                        cmdline.append(optionallyQuoteField(KettleVFS.getFilename(fileObject), "\""));

                    for (int i = 0; i < args.length; i++) {
                        cmdline.append(' ');
                        cmdline.append(optionallyQuoteField(args[i], "\""));
                    }
                    cmdline.append('"');
                    cmds.add(cmdline.toString());
                } else {
                    for (int i = 0; i < args.length; i++) {
                        cmds.add(args[i]);
                    }
                }
            }

            StringBuffer command = new StringBuffer();

            Iterator<String> it = cmds.iterator();
            boolean first = true;
            while (it.hasNext()) {
                if (!first)
                    command.append(' ');
                else
                    first = false;
                command.append((String) it.next());
            }
            if (log.isBasic())
                log.logBasic(toString(), Messages.getString("JobShell.ExecCommand", command.toString()));

            // Build the environment variable list...
            ProcessBuilder procBuilder = new ProcessBuilder(cmds);
            Map<String, String> env = procBuilder.environment();
            String[] variables = listVariables();
            for (int i = 0; i < variables.length; i++) {
                env.put(variables[i], getVariable(variables[i]));
            }

            if (getWorkDirectory() != null && !Const.isEmpty(Const.rtrim(getWorkDirectory()))) {
                String vfsFilename = environmentSubstitute(getWorkDirectory());
                File file = new File(KettleVFS.getFilename(KettleVFS.getFileObject(vfsFilename)));
                procBuilder.directory(file);
            }
            Process proc = procBuilder.start();

            // any error message?
            StreamLogger errorLogger = new StreamLogger(proc.getErrorStream(), toString() + " (stderr)");

            // any output?
            StreamLogger outputLogger = new StreamLogger(proc.getInputStream(), toString() + " (stdout)");

            // kick them off
            new Thread(errorLogger).start();
            new Thread(outputLogger).start();

            proc.waitFor();
            if (log.isDetailed())
                log.logDetailed(toString(), Messages.getString("JobShell.CommandFinished", command.toString()));

            // What's the exit status?
            result.setExitStatus(proc.exitValue());
            if (result.getExitStatus() != 0) {
                if (log.isDetailed())
                    log.logDetailed(toString(), Messages.getString("JobShell.ExitStatus",
                            environmentSubstitute(getFilename()), "" + result.getExitStatus()));

                result.setNrErrors(1);
            }

            // close the streams
            // otherwise you get "Too many open files, java.io.IOException" after a lot of iterations
            proc.getErrorStream().close();
            proc.getOutputStream().close();

        } catch (IOException ioe) {
            log.logError(toString(), Messages.getString("JobShell.ErrorRunningShell",
                    environmentSubstitute(getFilename()), ioe.toString()));
            result.setNrErrors(1);
        } catch (InterruptedException ie) {
            log.logError(toString(), Messages.getString("JobShell.Shellinterupted",
                    environmentSubstitute(getFilename()), ie.toString()));
            result.setNrErrors(1);
        } catch (Exception e) {
            log.logError(toString(), Messages.getString("JobShell.UnexpectedError",
                    environmentSubstitute(getFilename()), e.toString()));
            result.setNrErrors(1);
        } finally {
            // If we created a temporary file, remove it...
            //
            if (tempFile != null) {
                try {
                    tempFile.delete();
                } catch (Exception e) {
                    Messages.getString("JobShell.UnexpectedError", tempFile.toString(), e.toString());
                }
            }
        }

        if (result.getNrErrors() > 0) {
            result.setResult(false);
        } else {
            result.setResult(true);
        }
    }

    private String optionallyQuoteField(String field, String quote) {
        if (Const.isEmpty(field))
            return "\"\"";

        // If the field already contains quotes, we don't touch it anymore, just
        // return the same string...
        // also return it if no spaces are found
        if (field.indexOf(quote) >= 0 || (field.indexOf(' ') < 0 && field.indexOf('=') < 0)) {
            return field;
        } else {
            return quote + field + quote;
        }
    }

    public boolean evaluates() {
        return true;
    }

    public boolean isUnconditional() {
        return true;
    }

    public List<ResourceReference> getResourceDependencies(JobMeta jobMeta) {
        List<ResourceReference> references = super.getResourceDependencies(jobMeta);
        if (!Const.isEmpty(filename)) {
            String realFileName = jobMeta.environmentSubstitute(filename);
            ResourceReference reference = new ResourceReference(this);
            reference.getEntries().add(new ResourceEntry(realFileName, ResourceType.FILE));
            references.add(reference);
        }
        return references;
    }

    @Override
    public void check(List<CheckResultInterface> remarks, JobMeta jobMeta) {
        ValidatorContext ctx = new ValidatorContext();
        putVariableSpace(ctx, getVariables());
        putValidators(ctx, notBlankValidator(), fileExistsValidator());

        andValidator().validate(this, "workDirectory", remarks, ctx); //$NON-NLS-1$
        andValidator().validate(this, "filename", remarks, putValidators(notBlankValidator())); //$NON-NLS-1$

        if (setLogfile) {
            andValidator().validate(this, "logfile", remarks, putValidators(notBlankValidator())); //$NON-NLS-1$
        }
    }

    protected String getLogfile() {
        return logfile;
    }

    public String[] getArguments() {
        return arguments;
    }

    public void setArguments(String[] arguments) {
        this.arguments = arguments;
    }

}