cz.cas.lib.proarc.common.process.GenericExternalProcess.java Source code

Java tutorial

Introduction

Here is the source code for cz.cas.lib.proarc.common.process.GenericExternalProcess.java

Source

/*
 * Copyright (C) 2015 Jan Pokorsky
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package cz.cas.lib.proarc.common.process;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.io.FilenameUtils;

/**
 * The experimental implementation of an external process that can be customized
 * in more details including input and output parameters.
 *
 * @author Jan Pokorsky
 */
public class GenericExternalProcess extends ExternalProcess {

    public static final String DST_PARENT = "output.folder";
    public static final String DST_PATH = "output.file";
    public static final String SRC_EXT = "input.file.ext";
    public static final String SRC_NAME = "input.file.name";
    public static final String SRC_NAME_EXT = "input.file.nameExt";
    public static final String SRC_PATH = "input.file";
    public static final String SRC_PARENT = "input.folder";

    private File inputFile;
    private File outputFile;
    private final ParamHandler parameters;
    private final Configuration conf;
    private static final Pattern REPLACE_PARAM_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
    private boolean skippedProcess;
    private ProcessResult processResult;

    public GenericExternalProcess(Configuration conf) {
        super(conf);
        this.conf = conf;
        this.parameters = new ParamHandler();
    }

    public GenericExternalProcess addInputFile(File file) {
        this.inputFile = file;
        parameters.addInputFile(file);
        return this;
    }

    public File getInputFile() {
        return inputFile;
    }

    public GenericExternalProcess addOutputFile(File file) {
        this.outputFile = file;
        parameters.addOutput(file);
        return this;
    }

    public File getOutputFile() {
        return processResult == null ? outputFile : getResult().getOutputFile();
    }

    public ParamHandler getParameters() {
        return parameters;
    }

    public Map<String, String> getResultParameters() {
        return getResult().getParameters();
    }

    public ProcessResult getResult() {
        if (processResult == null) {
            throw new IllegalStateException("The process has not run yet!");
        }
        return processResult;
    }

    @Override
    public void run() {
        Configuration run = conf.subset("run");
        String[] ifNotExists = run.getStringArray("if.notExists");
        for (String ifNotExist : ifNotExists) {
            String path = interpolateParameters(ifNotExist, parameters.getMap());
            File file = new File(path);
            if (file.exists()) {
                skippedProcess = true;
                break;
            }
        }
        if (!skippedProcess) {
            super.run();
        }
        processResult = ProcessResult.getResultParameters(conf, parameters.getMap(), isSkippedProcess(),
                getExitCode(), getFullOutput());
    }

    public boolean isSkippedProcess() {
        return skippedProcess;
    }

    @Override
    protected List<String> buildCmdLine(Configuration conf) {
        List<String> cmdLine = super.buildCmdLine(conf);
        interpolateParameters(cmdLine);
        return cmdLine;
    }

    /**
     * Interpolate parameter values. The replace pattern is {@code ${name}}.
     * @param s a string to search for placeholders
     * @return the resolved string
     * @see #addParameter(java.lang.String, java.lang.String)
     */
    static String interpolateParameters(String s, Map<String, String> parameters) {
        if (s == null || s.length() < 4 || parameters.isEmpty()) { // minimal replaceable value ${x}
            return s;
        }
        // finds ${name} patterns
        Matcher m = REPLACE_PARAM_PATTERN.matcher(s);
        StringBuffer sb = null;
        while (m.find()) {
            if (m.groupCount() == 1) {
                String param = m.group(1);
                String replacement = parameters.get(param);
                if (replacement != null) {
                    sb = sb != null ? sb : new StringBuffer();
                    m.appendReplacement(sb, Matcher.quoteReplacement(replacement));
                }
            }
        }
        if (sb == null) {
            return s;
        }
        m.appendTail(sb);
        return sb.toString();
    }

    void interpolateParameters(List<String> cmdLine) {
        if (parameters.isEmpty()) {
            return;
        }
        for (ListIterator<String> it = cmdLine.listIterator(); it.hasNext();) {
            it.set(interpolateParameters(it.next(), parameters.getMap()));
        }
    }

    public static class ParamHandler {

        private final Map<String, String> parameters = new HashMap<String, String>();

        public ParamHandler add(Map<String, String> parameters) {
            this.parameters.putAll(parameters);
            return this;
        }

        public ParamHandler add(String name, String value) {
            parameters.put(name, value);
            return this;
        }

        public ParamHandler addInputFile(File file) {
            String nameExt = file.getName();
            return add(SRC_PATH, file.getAbsolutePath()).add(SRC_EXT, FilenameUtils.getExtension(nameExt))
                    .add(SRC_NAME, FilenameUtils.getBaseName(nameExt)).add(SRC_NAME_EXT, nameExt)
                    .add(SRC_PARENT, file.getParentFile().getPath());
        }

        public ParamHandler addOutput(File file) {
            return add(DST_PATH, file.getAbsolutePath()).add(DST_PARENT, file.getParentFile().getPath());
        }

        public Map<String, String> getMap() {
            return parameters;
        }

        public boolean isEmpty() {
            return parameters.isEmpty();
        }

    }

    public static class ProcessResult {

        private final Map<String, String> parameters;
        private final ExitStrategy exit;
        private final String processorId;

        public ProcessResult(String processorId, Map<String, String> params, ExitStrategy exit) {
            this.parameters = params;
            this.exit = exit;
            this.processorId = processorId;
        }

        public Map<String, String> getParameters() {
            return parameters;
        }

        public File getOutputFile() {
            String path = parameters.get(processorId + ".param." + DST_PATH);
            if (path == null) {
                path = parameters.get(DST_PATH);
            }
            return path == null || path.isEmpty() ? null : new File(path);
        }

        public ExitStrategy getExit() {
            return exit;
        }

        /**
         * Merges result parameters. {@code onExit} and {@code onSkip} are experimental features.
         *
         * @param conf
         * @param inputParameters
         * @param skippedProcess
         * @param exitCode
         * @param log
         * @return
         */
        public static ProcessResult getResultParameters(Configuration conf, Map<String, String> inputParameters,
                boolean skippedProcess, int exitCode, String log) {

            // gets <processorId>.param.* parameters with interpolated values
            // it allows to share properties among process and read helper values from process declaration (mime, output file, ...)
            String processorId = conf.getString("id");
            Map<String, String> hm = new HashMap<String, String>(inputParameters);
            addResultParamaters(conf, processorId, hm);

            ExitStrategy exit = new ExitStrategy();
            if (skippedProcess) {
                Configuration onSkip = conf.subset("onSkip");
                addResultParamaters(onSkip, processorId, hm);
                exit.setSkip(true);
                exit.setContinueWithProcessIds(Arrays.asList(onSkip.getStringArray("next")));
                return new ProcessResult(processorId, hm, exit);
            }

            exit.setExitCode(exitCode);
            String[] onExitIds = conf.getStringArray("onExits");
            Configuration onExitConf = conf.subset("onExit");
            boolean defaultExit = true;
            for (String onExitId : onExitIds) {
                if (isExitId(onExitId, exitCode)) {
                    Configuration onExitIdConf = onExitConf.subset(onExitId);
                    addResultParamaters(onExitIdConf, processorId, hm);
                    exit.setErrorMessage(onExitIdConf.getString("message"));
                    exit.setStop(onExitIdConf.getBoolean("stop", exitCode != 0));
                    exit.setContinueWithProcessIds(Arrays.asList(onExitIdConf.getStringArray("next")));
                    defaultExit = false;
                    break;
                }
            }
            if (defaultExit) {
                exit.setStop(exitCode != 0);
                exit.setErrorMessage(log);
            }
            return new ProcessResult(processorId, hm, exit);
        }

        private static Map<String, String> addResultParamaters(Configuration conf, String processorId,
                Map<String, String> result) {

            for (Iterator<String> it = conf.getKeys("param"); it.hasNext();) {
                String param = it.next();
                String value = interpolateParameters(conf.getString(param), result);
                String resultName = processorId == null ? param : processorId + '.' + param;
                result.put(resultName, value);
            }
            return result;
        }

        /**
         * Resolves whether the {@code exitExpression} matches to {@code exitCode}.
         * @param exitExpression syntax:
         *      {@code '*' | '>' exitCode | '<' exitCode | exitCode [',' exitCode]*}
         * @param exitCode
         * @return
         */
        private static boolean isExitId(String exitExpression, int exitCode) {
            boolean match = false;
            if ("*".equals(exitExpression)) {
                match = true;
            } else if (exitExpression.charAt(0) == '>') {
                try {
                    int code = Integer.parseInt(exitExpression.substring(1));
                    if (exitCode > code) {
                        match = true;
                    }
                } catch (NumberFormatException ex) {
                }
            } else if (exitExpression.charAt(0) == '<') {
                try {
                    int code = Integer.parseInt(exitExpression.substring(1));
                    if (exitCode < code) {
                        match = true;
                    }
                } catch (NumberFormatException ex) {
                }
            } else {
                String[] codes = exitExpression.split(",");
                for (String codeStr : codes) {
                    try {
                        int code = Integer.parseInt(codeStr.trim());
                        if (exitCode == code) {
                            match = true;
                        }
                    } catch (NumberFormatException ex) {
                    }
                }
            }
            return match;
        }
    }

    public static class ExitStrategy {

        private Integer exitCode;
        private boolean stop;
        private boolean skip;
        private List<String> continueWithProcessIds;
        private String errorMessage;

        public Integer getExitCode() {
            return exitCode;
        }

        public void setExitCode(Integer exitCode) {
            this.exitCode = exitCode;
        }

        public boolean isStop() {
            return stop;
        }

        public void setStop(boolean stop) {
            this.stop = stop;
        }

        public boolean isSkip() {
            return skip;
        }

        public void setSkip(boolean skip) {
            this.skip = skip;
        }

        public List<String> getContinueWithProcessIds() {
            return continueWithProcessIds;
        }

        public void setContinueWithProcessIds(List<String> continueWithProcessIds) {
            this.continueWithProcessIds = continueWithProcessIds;
        }

        public String getErrorMessage() {
            return errorMessage;
        }

        public void setErrorMessage(String errorMessage) {
            this.errorMessage = errorMessage;
        }

    }

}