Package hudson.plugins.accurev

Source Code of hudson.plugins.accurev.AccurevLauncher$ICmdOutputXmlParser

package hudson.plugins.accurev;

import hudson.FilePath;
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.model.TaskListener;
import hudson.util.ArgumentListBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

/**
* Utility class that knows how to run AccuRev commands and (optionally) have
* something parse their output.
*/
public final class AccurevLauncher {

    /**
     * Interface implemented by code that interprets the output of AccuRev
     * commands.
     * <p>
     * Intended to separate out the running of commands from the actual parsing
     * of their results in an attempt to reduce code duplication.
     *
     * @param <TResult>
     *            The output of the parsing process.
     * @param <TContext>
     *            Context object that will be passed to the parser each time it
     *            is called.
     */
    public interface ICmdOutputParser<TResult, TContext> {
        /**
         * Parses the command's output.
         *
         * @param cmdOutput
         *            The stream that contains the output of the command.
         * @param context
         *            Context passed in when the command was run.
         * @return The result of the parsing.
         * @throws UnhandledAccurevCommandOutput
         *             if the command output was invalid.
         */
        TResult parse(InputStream cmdOutput, TContext context) throws UnhandledAccurevCommandOutput, IOException;
    }

    /**
     * Interface implemented by code that interprets the output of AccuRev
     * commands.
     * <p>
     * Intended to separate out the running of commands from the actual parsing
     * of their results in an attempt to reduce code duplication.
     *
     * @param <TResult>
     *            The output of the parsing process.
     * @param <TContext>
     *            Context object that will be passed to the parser each time it
     *            is called.
     */
    public interface ICmdOutputXmlParser<TResult, TContext> {
        /**
         * Parses the command's output.
         *
         * @param parser
         *            The {@link XmlPullParser} that contains the output of the
         *            command.
         * @param context
         *            Context passed in when the command was run.
         * @return The result of the parsing.
         * @throws UnhandledAccurevCommandOutput
         *             if the command output was invalid.
         */
        TResult parse(XmlPullParser parser, TContext context) throws UnhandledAccurevCommandOutput, IOException,
                XmlPullParserException;
    }

    /**
     * Exception that can be throw if the AccuRev command's output cannot be
     * parsed or is otherwise invalid.
     */
    public static final class UnhandledAccurevCommandOutput extends Exception {
        public UnhandledAccurevCommandOutput(String message, Throwable cause) {
            super(message, cause);
        }

        public UnhandledAccurevCommandOutput(String message) {
            super(message);
        }

        public UnhandledAccurevCommandOutput(Throwable cause) {
            super(cause);
        }
    }

    /**
     * Runs a command and returns <code>true</code> if it passed,
     * <code>false</code> if it failed, and logs the errors.
     *
     * @param humanReadableCommandName
     *            Human-readable text saying what this command is. This appears
     *            in the logs if there is a failure.
     * @param launcher
     *            Means of executing the command.
     * @param machineReadableCommand
     *            The command to be executed.
     * @param masksOrNull
     *            Argument for {@link ProcStarter#masks(boolean...)}, or
     *            <code>null</code> if not required.
     * @param synchronizationLockObjectOrNull
     *            The {@link Lock} object to be used to prevent concurrent
     *            execution on the same machine, or <code>null</code> if no
     *            synchronization is required.
     * @param environmentVariables
     *            The environment variables to be passed to the command.
     * @param directoryToRunCommandFrom
     *            The direction that the command should be run in.
     * @param listenerToLogFailuresTo
     *            One possible place to log failures, or <code>null</code>.
     * @param loggerToLogFailuresTo
     *            Another place to log failures, or <code>null</code>.
     * @param optionalFlagToCopyAllOutputToTaskListener
     *            Optional: If present and <code>true</code>, all command output
     *            will also be copied to the listener if the command is
     *            successful.
     * @return <code>true</code> if the command succeeded.
     */
    public static boolean runCommand(//
            final String humanReadableCommandName, //
            final Launcher launcher, //
            final ArgumentListBuilder machineReadableCommand, //
            final boolean[] masksOrNull, //
            final Lock synchronizationLockObjectOrNull, //
            final Map<String, String> environmentVariables, //
            final FilePath directoryToRunCommandFrom, //
            final TaskListener listenerToLogFailuresTo, //
            final Logger loggerToLogFailuresTo, //
            final boolean... optionalFlagToCopyAllOutputToTaskListener) {
        final Boolean result;
        final boolean shouldLogEverything = optionalFlagToCopyAllOutputToTaskListener != null
                && optionalFlagToCopyAllOutputToTaskListener.length > 0 && optionalFlagToCopyAllOutputToTaskListener[0];
        if (shouldLogEverything) {
            result = runCommand(humanReadableCommandName, launcher, machineReadableCommand, masksOrNull,
                    synchronizationLockObjectOrNull, environmentVariables, directoryToRunCommandFrom,
                    listenerToLogFailuresTo, loggerToLogFailuresTo, new ParseOutputToStream(),
                    listenerToLogFailuresTo.getLogger());
        } else {
            result = runCommand(humanReadableCommandName, launcher, machineReadableCommand, masksOrNull,
                    synchronizationLockObjectOrNull, environmentVariables, directoryToRunCommandFrom,
                    listenerToLogFailuresTo, loggerToLogFailuresTo, new ParseIgnoreOutput(), null);
        }
        return result == Boolean.TRUE;
    }

    /**
     * As
     * {@link #runCommand(String, Launcher, ArgumentListBuilder, boolean[], Lock, Map, FilePath, TaskListener, Logger, ICmdOutputParser, Object)}
     * but uses an {@link ICmdOutputXmlParser} instead.
     *
     * @param xmlParserFactory
     *            The {@link XmlPullParserFactory} to be used to create the
     *            parser. If this is <code>null</code> then no command will be
     *            executed and the function will return <code>null</code>
     *            immediately.
     * @return See above.
     */
    public static <TResult, TContext> TResult runCommand(//
            final String humanReadableCommandName, //
            final Launcher launcher, //
            final ArgumentListBuilder machineReadableCommand, //
            final boolean[] masksOrNull, //
            final Lock synchronizationLockObjectOrNull, //
            final Map<String, String> environmentVariables, //
            final FilePath directoryToRunCommandFrom, //
            final TaskListener listenerToLogFailuresTo, //
            final Logger loggerToLogFailuresTo, //
            final XmlPullParserFactory xmlParserFactory, //
            final ICmdOutputXmlParser<TResult, TContext> commandOutputParser, //
            final TContext commandOutputParserContext) {
        return runCommand(humanReadableCommandName, launcher, machineReadableCommand, masksOrNull,
                synchronizationLockObjectOrNull, environmentVariables, directoryToRunCommandFrom,
                listenerToLogFailuresTo, loggerToLogFailuresTo, new ICmdOutputParser<TResult, TContext>() {
                    public TResult parse(InputStream cmdOutput, TContext context) throws UnhandledAccurevCommandOutput,
                            IOException {
                        XmlPullParser parser = null;
                        try {
                            parser = xmlParserFactory.newPullParser();
                            parser.setInput(cmdOutput, null);
                            final TResult result = commandOutputParser.parse(parser, context);
                            parser.setInput(null);
                            parser = null;
                            return result;
                        } catch (XmlPullParserException ex) {
                            logCommandException(machineReadableCommand, directoryToRunCommandFrom,
                                    humanReadableCommandName, ex, loggerToLogFailuresTo, listenerToLogFailuresTo);
                            return null;
                        } finally {
                            if (parser != null) {
                                try {
                                    parser.setInput(null);
                                } catch (XmlPullParserException ex) {
                                    logCommandException(machineReadableCommand, directoryToRunCommandFrom,
                                            humanReadableCommandName, ex, loggerToLogFailuresTo,
                                            listenerToLogFailuresTo);
                                }
                                cmdOutput.close();
                            }
                        }
                    }
                }, commandOutputParserContext);
    }

    /**
     * Runs a command a parses the output, returning the result of parsing that
     * output. Returns <code>null</code> if the command failed or if parsing
     * failed. Failures are logged.
     *
     * @param <TResult>
     *            The type of the result returned by the parser.
     * @param <TContext>
     *            The type of data to be passed to the parser. Can be
     *            {@link Void} if no result is needed.
     * @param humanReadableCommandName
     *            Human-readable text saying what this command is. This appears
     *            in the logs if there is a failure.
     * @param launcher
     *            Means of executing the command.
     * @param machineReadableCommand
     *            The command to be executed.
     * @param masksOrNull
     *            Argument for {@link ProcStarter#masks(boolean...)}, or
     *            <code>null</code> if not required.
     * @param synchronizationLockObjectOrNull
     *            The {@link Lock} object to be used to prevent concurrent
     *            execution on the same machine, or <code>null</code> if no
     *            synchronization is required.
     * @param environmentVariables
     *            The environment variables to be passed to the command.
     * @param directoryToRunCommandFrom
     *            The direction that the command should be run in.
     * @param listenerToLogFailuresTo
     *            One possible place to log failures, or <code>null</code>.
     * @param loggerToLogFailuresTo
     *            Another place to log failures, or <code>null</code>.
     * @param commandOutputParser
     *            The code that will parse the command's output (if the command
     *            succeeds).
     * @param commandOutputParserContext
     *            Data to be passed to the parser.
     * @return The data returned by the {@link ICmdOutputParser}, or
     *         <code>null</code> if an error occurred.
     */
    public static <TResult, TContext> TResult runCommand(//
            final String humanReadableCommandName, //
            final Launcher launcher, //
            final ArgumentListBuilder machineReadableCommand, //
            final boolean[] masksOrNull, //
            final Lock synchronizationLockObjectOrNull, //
            final Map<String, String> environmentVariables, //
            final FilePath directoryToRunCommandFrom, //
            final TaskListener listenerToLogFailuresTo, //
            final Logger loggerToLogFailuresTo, //
            final ICmdOutputParser<TResult, TContext> commandOutputParser, //
            final TContext commandOutputParserContext) {
        final ByteArrayStream stdout = new ByteArrayStream();
        final ByteArrayStream stderr = new ByteArrayStream();
        try {
            final OutputStream stdoutStream = stdout.getOutput();
            final OutputStream stderrStream = stderr.getOutput();
            final ProcStarter starter = createProcess(launcher, machineReadableCommand, masksOrNull,
                    environmentVariables, directoryToRunCommandFrom, stdoutStream, stderrStream);
            logCommandExecution(machineReadableCommand, directoryToRunCommandFrom, loggerToLogFailuresTo,
                    listenerToLogFailuresTo);
            try {
                final int commandExitCode = runCommandToCompletion(starter, synchronizationLockObjectOrNull);
                final InputStream outputFromCommand = stdout.getInput();
                final InputStream errorFromCommand = stderr.getInput();
                if (commandExitCode != 0) {
                    logCommandFailure(machineReadableCommand, directoryToRunCommandFrom, humanReadableCommandName,
                            commandExitCode, outputFromCommand, errorFromCommand, loggerToLogFailuresTo, listenerToLogFailuresTo);
                    return null;
                }
                final TResult parsedResult = commandOutputParser.parse(outputFromCommand, commandOutputParserContext);
                return parsedResult;
            } catch (Exception ex) {
                logCommandException(machineReadableCommand, directoryToRunCommandFrom, humanReadableCommandName, ex,
                        loggerToLogFailuresTo, listenerToLogFailuresTo);
                return null;
            }
        } finally {
            try {
                stdout.close();
                stderr.close();
            } catch (IOException ex) {
                logCommandException(machineReadableCommand, directoryToRunCommandFrom, humanReadableCommandName, ex,
                        loggerToLogFailuresTo, listenerToLogFailuresTo);
            }
        }
    }

    private static Integer runCommandToCompletion(//
            final ProcStarter starter, //
            final Lock synchronizationLockObjectOrNull) throws IOException, InterruptedException {
        try {
            if (synchronizationLockObjectOrNull != null) {
                synchronizationLockObjectOrNull.lock();
            }
            final int commandExitCode = starter.join();
            return Integer.valueOf(commandExitCode);
        } finally {
            if (synchronizationLockObjectOrNull != null) {
                synchronizationLockObjectOrNull.unlock();
            }
        }
    }

    private static ProcStarter createProcess(//
            final Launcher launcher, //
            final ArgumentListBuilder machineReadableCommand, //
            final boolean[] masksOrNull, //
            final Map<String, String> environmentVariables, //
            final FilePath directoryToRunCommandFrom, //
            final OutputStream stdoutStream, //
            final OutputStream stderrStream) {
        ProcStarter starter = launcher.launch().cmds(machineReadableCommand);
        if (masksOrNull != null) {
            starter = starter.masks(masksOrNull);
        }
        starter = starter.envs(environmentVariables);
        starter = starter.stdout(stdoutStream).stderr(stderrStream);
        starter = starter.pwd(directoryToRunCommandFrom);
        return starter;
    }

    private static void logCommandFailure(//
            final ArgumentListBuilder command, //
            final FilePath directoryToRunCommandFrom, //
            final String commandDescription, //
            final int commandExitCode, //
            final InputStream commandStdoutOrNull, //
            final InputStream commandStderrOrNull, //
            final Logger loggerToLogFailuresTo, //
            final TaskListener taskListener) {
        final String msg = commandDescription + " (" + command.toStringWithQuote() + ")" + " failed with exit code "
                + commandExitCode;
        String stderr = null;
        try {
            stderr = getCommandErrorOutput(commandStdoutOrNull, commandStderrOrNull);
        } catch (IOException ex) {
            logCommandException(command, directoryToRunCommandFrom, commandDescription, ex, loggerToLogFailuresTo,
                    taskListener);
        }
        if (loggerToLogFailuresTo != null
                && (loggerToLogFailuresTo.isLoggable(Level.WARNING) || loggerToLogFailuresTo.isLoggable(Level.INFO))) {
            final String hostname = getRemoteHostname(directoryToRunCommandFrom);
            loggerToLogFailuresTo.warning(hostname + ": " + msg);
            if (stderr != null) {
                loggerToLogFailuresTo.info(hostname + ": " + stderr);
            }
        }
        if (taskListener != null) {
            if (stderr != null) {
                taskListener.fatalError(stderr);
            }
            taskListener.fatalError(msg);
        }
    }

    private static String getCommandErrorOutput(final InputStream commandStdoutOrNull,
            final InputStream commandStderrOrNull) throws IOException {
        final Integer maxNumberOfStderrLines = Integer.valueOf(10);
        final Integer maxNumberOfStdoutLines = Integer.valueOf(5);
        final String newLine = (String) System.getProperty("line.separator");
        final ParseLastFewLines tailParser = new ParseLastFewLines();
        final StringBuilder outputText = new StringBuilder();
        if (commandStdoutOrNull != null) {
            final List<String> stdoutLines = tailParser.parse(commandStdoutOrNull, maxNumberOfStdoutLines);
            for (final String line : stdoutLines) {
                if (outputText.length() > 0) {
                    outputText.append(newLine);
                }
                outputText.append(line);
            }
        }
        if (commandStderrOrNull != null) {
            final List<String> stderrLines = tailParser.parse(commandStderrOrNull, maxNumberOfStderrLines);
            for (final String line : stderrLines) {
                if (outputText.length() > 0) {
                    outputText.append(newLine);
                }
                outputText.append(line);
            }
        }
        if (outputText.length() > 0) {
            return outputText.toString();
        } else {
            return null;
        }
    }

    private static void logCommandException(//
            final ArgumentListBuilder command, //
            final FilePath directoryToRunCommandFrom, //
            final String commandDescription, //
            final Throwable exception, //
            final Logger loggerToLogFailuresTo, //
            final TaskListener taskListener) {
        final String hostname = getRemoteHostname(directoryToRunCommandFrom);
        final String msg = hostname + ": " + commandDescription + " (" + command.toStringWithQuote() + ")"
                + " failed with " + exception.toString();
        logException(msg, exception, loggerToLogFailuresTo, taskListener);
    }

    static void logException(//
            final String summary, //
            final Throwable exception, //
            final Logger logger, //
            final TaskListener taskListener) {
        if (logger != null) {
            // TODO: Log the machine name to the Logger as well.
            logger.log(Level.SEVERE, exception.getMessage(), exception);
        }
        if (taskListener != null) {
            taskListener.fatalError(summary);
            exception.printStackTrace(taskListener.getLogger());
        }
    }

    private static void logCommandExecution(//
            final ArgumentListBuilder command, //
            final FilePath directoryToRunCommandFrom, //
            final Logger loggerToLogFailuresTo, //
            final TaskListener taskListener) {
        if (loggerToLogFailuresTo != null && loggerToLogFailuresTo.isLoggable(Level.FINE)) {
            final String hostname = getRemoteHostname(directoryToRunCommandFrom);
            final String msg = hostname + ": " + command.toStringWithQuote();
            loggerToLogFailuresTo.log(Level.FINE, msg);
        }
    }

    private static String getRemoteHostname(final FilePath directoryToRunCommandFrom) {
        try {
            final RemoteWorkspaceDetails act = directoryToRunCommandFrom.act(new DetermineRemoteHostname("."));
            final String hostName = act.getHostName();
            return hostName;
        } catch (UnknownHostException e) {
            return e.toString();
        } catch (IOException e) {
            return e.toString();
        } catch (InterruptedException e) {
            return e.toString();
        }
    }
}
TOP

Related Classes of hudson.plugins.accurev.AccurevLauncher$ICmdOutputXmlParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.