org.oxymores.chronix.engine.RunnerShell.java Source code

Java tutorial

Introduction

Here is the source code for org.oxymores.chronix.engine.RunnerShell.java

Source

/**
 * By Marc-Antoine Gouillart, 2012
 * 
 * See the NOTICE file distributed with this work for 
 * information regarding copyright ownership.
 * This file is licensed to you 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 org.oxymores.chronix.engine;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.oxymores.chronix.engine.data.RunDescription;
import org.oxymores.chronix.engine.data.RunResult;

public final class RunnerShell {
    private RunnerShell() {

    }

    // RunnerAgent logger, yes.
    private static Logger log = Logger.getLogger(RunnerAgent.class);
    private static String POWERSHELL_CMD = "powershell.exe", CMD_CMD = "cmd.exe";

    public static RunResult run(RunDescription rd, String logFilePath, boolean storeLogFile,
            boolean returnFullerLog) {
        RunResult res = new RunResult();
        Process p;
        String nl = System.getProperty("line.separator");
        Pattern pat = Pattern.compile("^set ([a-zA-Z]+[a-zA-Z0-9]*)=(.+)");
        Matcher matcher = pat.matcher("Testing123Testing");
        String encoding = getEncoding(rd);
        log.debug("Encoding is " + encoding);

        // ///////////////////////////
        // Build command
        List<String> argsStrings = buildCommand(rd);

        // /////////////////////////////////////////////////////////////////////////
        // Create a process builder with the command line contained in the array
        ProcessBuilder pb = new ProcessBuilder(argsStrings);

        // Mix stdout and stderr (easier to put errors in context this way)
        pb.redirectErrorStream(true);

        // Create array containing environment
        Map<String, String> env = pb.environment();
        for (int i = 0; i < rd.getEnvNames().size(); i++) {
            env.put(rd.getEnvNames().get(i), rd.getEnvValues().get(i));
        }

        BufferedReader br = null;
        Writer output = null;
        try {
            // Start!
            log.debug("GO (" + rd.getSubMethod() + ")");
            p = pb.start();

            // Read output (err & out), write it to file
            InputStreamReader isr = new InputStreamReader(p.getInputStream(), encoding);
            br = new BufferedReader(isr);

            String line = null;
            int i = 0;
            LinkedHashMap<Integer, String> endBuffer = new LinkedHashMap<Integer, String>() {
                private static final long serialVersionUID = -6773540176968046737L;

                @Override
                protected boolean removeEldestEntry(java.util.Map.Entry<Integer, String> eldest) {
                    return this.size() > Constants.MAX_RETURNED_BIG_LOG_END_LINES;
                }
            };

            if (storeLogFile) {
                output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(logFilePath), "UTF-8"));
            }
            line = br.readLine();
            while (line != null) {
                i++;

                // Local log file gets all lines
                if (storeLogFile) {
                    output.write(line + nl);
                }

                // Small log gets first 500 lines or 10000 characters (the smaller of the two)
                if (i < Constants.MAX_RETURNED_SMALL_LOG_LINES
                        && res.logStart.length() < Constants.MAX_RETURNED_SMALL_LOG_CHARACTERS) {
                    res.logStart += nl + line;
                }

                // Scheduler internal log gets first line only
                if (i == 1) {
                    log.debug(String.format("Job running. First line of output is: %s", line));
                }

                // Fuller log gets first 10k lines, then last 1k lines.
                if (returnFullerLog) {
                    if (i < Constants.MAX_RETURNED_BIG_LOG_LINES) {
                        res.fullerLog += line;
                    } else {
                        endBuffer.put(i, line);
                    }
                }

                // Analysis: there may be a new variable definition in the line
                matcher.reset(line);
                if (matcher.find()) {
                    log.debug("Key detected :" + matcher.group(1));
                    log.debug("Value detected :" + matcher.group(2));
                    res.newEnvVars.put(matcher.group(1), matcher.group(2));
                }
                line = br.readLine();
            }
            IOUtils.closeQuietly(br);

            if (i > Constants.MAX_RETURNED_BIG_LOG_LINES
                    && i < Constants.MAX_RETURNED_BIG_LOG_LINES + Constants.MAX_RETURNED_BIG_LOG_END_LINES
                    && returnFullerLog) {
                res.fullerLog += Arrays.toString(endBuffer.entrySet().toArray());
            }
            if (i >= Constants.MAX_RETURNED_BIG_LOG_LINES + Constants.MAX_RETURNED_BIG_LOG_END_LINES
                    && returnFullerLog) {
                res.fullerLog += "\n\n\n*******\n LOG TRUNCATED - See full log on server\n********\n\n\n"
                        + Arrays.toString(endBuffer.entrySet().toArray());
            }

            // Done: close log file
            if (storeLogFile) {
                IOUtils.closeQuietly(output);
                File f = new File(logFilePath);
                res.logSizeBytes = f.length();
            }
        } catch (IOException e) {
            log.error("error occurred while running job", e);
            res.logStart = e.getMessage();
            res.returnCode = -1;
            IOUtils.closeQuietly(br);
            IOUtils.closeQuietly(output);
            return res;
        }

        // Return
        res.returnCode = p.exitValue();
        res.logPath = logFilePath;
        res.envtUser = System.getProperty("user.name");
        try {
            res.envtServer = InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            res.envtServer = "unknown";
        }
        log.info(String.format("Job ended, RC is %s", res.returnCode));
        return res;
    }

    private static String getEncoding(RunDescription rd) {
        String encoding = System.getProperty("file.encoding");
        if (rd.getSubMethod().equals(CMD_CMD) || rd.getSubMethod().equals(POWERSHELL_CMD)) {
            try {
                encoding = "cp" + WinRegistry.readString(WinRegistry.HKEY_LOCAL_MACHINE,
                        "SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage", "OEMCP");
            } catch (Exception e) {
                log.warn(
                        "Windows console encoding could not be found. Default encoding will be used. Logs may be encoded weirdly",
                        e);
            }
        }
        return encoding;
    }

    private static List<String> buildCommand(RunDescription rd) {
        ArrayList<String> argsStrings = new ArrayList<String>();

        // First line is the shell name
        argsStrings.add(rd.getSubMethod());

        // Depending on the shell, we may have to add shell start parameters to allow batch processing (Windows only)
        if (rd.getSubMethod().equals(CMD_CMD)) {
            argsStrings.add("/C");
        } else if (rd.getSubMethod().equals(POWERSHELL_CMD)) {
            argsStrings.add("-NoLogo");
            argsStrings.add("-NonInteractive");
            argsStrings.add("-WindowStyle");
            argsStrings.add("Hidden");
            argsStrings.add("-Command");
        }

        // Then add the command itself
        argsStrings.add(rd.getCommand());

        // Finally add parameters (if any - there may be none or they may be contained inside the command itself)
        for (int i = 0; i < rd.getParamNames().size(); i++) {
            String key = rd.getParamNames().get(i);
            String value = rd.getParamValues().get(i);
            String arg = "";
            if (key != null && !"".equals(key)) {
                arg = key;
            }
            if (value != null && !"".equals(value) && (key != null && !"".equals(key))) {
                arg += " " + value;
            }
            if (value != null && !"".equals(value) && !(key != null && !"".equals(key))) {
                arg += value;
            }

            argsStrings.add(arg);
        }

        return argsStrings;
    }
}