org.fuin.esmp.EventStoreStartMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.fuin.esmp.EventStoreStartMojo.java

Source

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

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DaemonExecutor;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.OS;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Starts the event store.
 * 
 */
@Mojo(name = "start", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresProject = false)
public final class EventStoreStartMojo extends AbstractEventStoreMojo {

    private static final Logger LOG = LoggerFactory.getLogger(EventStoreStartMojo.class);

    /**
     * Name of the executable or shell script to start the event store. Defaults
     * to the OS specific name for Windows, Linux and Mac OS families. Other OS
     * families will cause an error if this value is not set.
     * 
     */
    @Parameter(name = "command")
    private String command;

    /**
     * Command line arguments to pass to the executable. If no arguments are set
     * this defaults to <code>--mem-db=TRUE</code>.
     * 
     */
    @Parameter(name = "arguments")
    private String[] arguments;

    /**
     * Number of times to wait for the server until it's up and running. After
     * this time passed, the build will fail. This means the mojo will wait
     * <code>maxWaitCycles</code> * <code>sleepMs</code> milliseconds for the
     * server to finish it's startup process. Defaults to 20 times.
     * 
     */
    @Parameter(name = "max-wait-cycles", defaultValue = "20")
    private int maxWaitCycles = 20;

    /**
     * Number of milliseconds to sleep while waiting for the server. This means
     * the mojo will wait <code>maxWaitCycles</code> * <code>sleepMs</code>
     * milliseconds for the server to finish it's startup process. Defaults to
     * 500 ms.
     * 
     */
    @Parameter(name = "sleep-ms", defaultValue = "500")
    private int sleepMs = 500;

    /**
     * Message from the event store log to wait for.
     * 
     */
    @Parameter(name = "up-message", defaultValue = "'admin' user account has been created")
    private String upMessage = "'admin' user account has been created";

    @Override
    protected final void executeGoal() throws MojoExecutionException {
        init();

        LOG.info("command={}", command);
        LOG.info("arguments={}", Arrays.toString(arguments));

        final CommandLine cmdLine = createCommandLine();
        final DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
        final DaemonExecutor executor = new DaemonExecutor();
        try {
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
            final PumpStreamHandler psh = new PumpStreamHandler(bos);
            executor.setStreamHandler(psh);
            executor.setWorkingDirectory(getEventStoreDir());
            executor.execute(cmdLine, resultHandler);
            final List<String> messages = waitForHttpServer(resultHandler, bos);
            logDebug(messages);
            final String pid = extractPid(messages);
            LOG.info("Event store process ID: {}", pid);
            writePid(pid);
        } catch (final IOException ex) {
            throw new MojoExecutionException("Error executing the command line: " + cmdLine, ex);
        }
    }

    private List<String> waitForHttpServer(final DefaultExecuteResultHandler resultHandler,
            final ByteArrayOutputStream bos) throws MojoExecutionException {

        // Wait for result
        int wait = 0;
        while ((wait++ < maxWaitCycles) && !resultHandler.hasResult() && !bos.toString().contains(upMessage)) {
            sleep(sleepMs);
        }

        if (bos.toString().contains(upMessage)) {
            // Success
            return asList(bos.toString());
        }

        // Failure
        final List<String> messages = asList(bos.toString());
        logError(messages);

        // Exception
        if (resultHandler.hasResult()) {
            throw new MojoExecutionException("Error starting the server. Exit code=" + resultHandler.getExitValue(),
                    resultHandler.getException());
        }
        // Timeout
        throw new MojoExecutionException("Waited too long for the server to start!");

    }

    private void sleep(final int ms) {
        try {
            Thread.sleep(ms);
        } catch (final InterruptedException ex) {
            LOG.info("Interrupted while sleeping", ex);
        }
    }

    private String extractPid(final List<String> messages) throws MojoExecutionException {
        if (messages.size() == 0) {
            throw new MojoExecutionException("Starting the event store didn't return any messages");
        }
        final String first = messages.get(0);
        // Prefix looks like this: [19648,10,12:47:52.297]
        final int p0 = first.indexOf('[');
        if (p0 == -1) {
            throw new MojoExecutionException("Couldn't locate the starting bracket '[': " + first);
        }
        final int p1 = first.indexOf(',', p0 + 1);
        if (p1 == -1) {
            throw new MojoExecutionException("Couldn't locate the ending comma ',': " + first);
        }
        return first.substring(p0 + 1, p1);
    }

    private void init() throws MojoExecutionException {

        // Supply variables that are OS dependent
        if (OS.isFamilyWindows()) {
            if (command == null) {
                // For some strange reasons this does not work without the
                // path...
                command = getEventStoreDir() + File.separator + "EventStore.ClusterNode.exe";
            }
        } else if (OS.isFamilyUnix()) {
            if (command == null) {
                command = "./run-node.sh";
            }
        } else if (OS.isFamilyMac()) {
            if (command == null) {
                command = "./run-node.sh";
            }
        } else {
            if (command == null) {
                throw new MojoExecutionException("Unknown OS - You must use the 'command' parameter");
            }
        }

        // Use in-memory mode if nothing else is set
        if (arguments == null) {
            arguments = new String[1];
            arguments[0] = "--mem-db=TRUE";
        }

    }

    private CommandLine createCommandLine() throws MojoExecutionException {
        final CommandLine cmdLine = new CommandLine(command);
        if (arguments != null) {
            for (final String argument : arguments) {
                cmdLine.addArgument(argument);
            }
        }
        return cmdLine;
    }

    /**
     * Returns the name of the executable or shell script to start the event
     * store.
     * 
     * @return Executable name.
     */
    public final String getCommand() {
        return command;
    }

    /**
     * Sets the name of the executable or shell script to start the event store.
     * 
     * @param command
     *            Executable name to set.
     */
    public final void setCommand(final String command) {
        this.command = command;
    }

    /**
     * Returns the command line arguments to pass to the executable.
     * 
     * @return Command line arguments.
     */
    public final String[] getArguments() {
        return arguments;
    }

    /**
     * Sets the command line arguments to pass to the executable.
     * 
     * @param arguments
     *            Arguments to set
     */
    public final void setArguments(final String[] arguments) {
        this.arguments = arguments;
    }

}