All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.as.plugin.server.Server Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.plugin.server;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.jboss.as.controller.client.ModelControllerClient;

/**
 * @author James R. Perkins
 */
abstract class Server {
    private final ScheduledExecutorService timerService;
    private final ServerConfig serverConfig;
    private Process process;
    private ConsoleConsumer console;
    private final String shutdownId;

    protected Server(final ServerConfig serverConfig) {
        this(serverConfig, null);
    }

    protected Server(final ServerConfig serverConfig, final String shutdownId) {
        this.serverConfig = serverConfig;
        this.shutdownId = shutdownId;
        timerService = Executors.newScheduledThreadPool(1);
    }

    /**
     * The console that is associated with the server.
     *
     * @return the console
     */
    protected final ConsoleConsumer getConsole() {
        return console;
    }

    /**
     * Starts the server.
     *
     * @throws IOException the an error occurs creating the process
     */
    public final synchronized void start() throws IOException {
        SecurityActions.registerShutdown(this);
        final List cmd = createLaunchCommand();
        final ProcessBuilder processBuilder = new ProcessBuilder(cmd);
        processBuilder.redirectErrorStream(true);
        process = processBuilder.start();
        console = startConsoleConsumer(process.getInputStream(), shutdownId);
        long timeout = serverConfig.getStartupTimeout() * 1000;
        boolean serverAvailable = false;
        long sleep = 50;
        init();
        while (timeout > 0 && !serverAvailable) {
            serverAvailable = isRunning();
            if (!serverAvailable) {
                if (processHasDied(process))
                    break;
                try {
                    Thread.sleep(sleep);
                } catch (InterruptedException e) {
                    serverAvailable = false;
                    break;
                }
                timeout -= sleep;
                sleep = Math.max(sleep / 2, 100);
            }
        }
        if (serverAvailable) {
            timerService.scheduleWithFixedDelay(new Reaper(), 20, 10, TimeUnit.SECONDS);
        } else {
            destroyProcess();
            throw new IllegalStateException(String.format("Managed server was not started within [%d] s", serverConfig.getStartupTimeout()));
        }
    }

    /**
     * Stops the server.
     */
    public final synchronized void stop() {
        try {
            stopServer();
        } finally {
            if (process != null) {
                process.destroy();
                try {
                    process.waitFor();
                } catch (InterruptedException ignore) {
                    // no-op
                }
            }
            timerService.shutdown();
        }
    }

    /**
     * Invokes any optional initialization that should take place after the process has been launched. Note the server
     * may not be completely started when the method is invoked.
     *
     * @throws IOException if an IO error occurs
     */
    protected abstract void init() throws IOException;

    /**
     * Stops the server before the process is destroyed. A no-op override will just destroy the process.
     */
    protected abstract void stopServer();

    /**
     * Checks the status of the server and returns {@code true} if the server is fully started.
     *
     * @return {@code true} if the server is fully started, otherwise {@code false}
     */
    public abstract boolean isRunning();

    /**
     * Returns the client that used to execute management operations on the server.
     *
     * @return the client to execute management operations
     */
    public abstract ModelControllerClient getClient();

    /**
     * Creates the command to launch the server for the process.
     *
     * @return the commands used to launch the server
     */
    protected abstract List createLaunchCommand();

    /**
     * Checks whether the server is running or not. If the server is no longer running the {@link #isRunning()} should
     * return {@code false}.
     */
    protected abstract void checkServerState();

    private int destroyProcess() {
        if (process == null)
            return 0;
        process.destroy();
        try {
            return process.waitFor();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean processHasDied(final Process process) {
        try {
            process.exitValue();
            return true;
        } catch (IllegalThreadStateException e) {
            // good
            return false;
        }
    }

    private ConsoleConsumer startConsoleConsumer(final InputStream stream, final String shutdownId) {
        final ConsoleConsumer result = new ConsoleConsumer(stream, shutdownId);
        final Thread t = new Thread(result);
        t.setName("AS7-Console");
        t.start();
        return result;
    }

    private class Reaper implements Runnable {

        @Override
        public void run() {
            checkServerState();
            if (!isRunning()) {
                stop();
            }
        }
    }

    /**
     * Runnable that consumes the output of the process.
     *
     * @author James R. Perkins
     */
    class ConsoleConsumer implements Runnable {

        private final InputStream in;
        private final String shutdownId;
        private final CountDownLatch latch;

        protected ConsoleConsumer(final InputStream in, final String shutdownId) {
            this.in = in;
            latch = new CountDownLatch(1);
            this.shutdownId = shutdownId;
        }

        @Override
        public void run() {

            try {
                byte[] buf = new byte[512];
                int num;
                while ((num = in.read(buf)) != -1) {
                    System.out.write(buf, 0, num);
                    if (shutdownId != null && new String(buf).contains(shutdownId)) {
                        latch.countDown();
                        if (isRunning()) {
                            stop();
                        }
                    }
                }
            } catch (IOException ignore) {
            }
        }

        void awaitShutdown(final long seconds) throws InterruptedException {
            if (shutdownId == null) latch.countDown();
            latch.await(seconds, TimeUnit.SECONDS);
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy