fi.jumi.launcher.remote.ProcessStartingDaemonSummoner.java Source code

Java tutorial

Introduction

Here is the source code for fi.jumi.launcher.remote.ProcessStartingDaemonSummoner.java

Source

// Copyright  2011-2014, Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package fi.jumi.launcher.remote;

import fi.jumi.actors.ActorRef;
import fi.jumi.actors.eventizers.Event;
import fi.jumi.actors.eventizers.dynamic.DynamicEventizer;
import fi.jumi.actors.queue.*;
import fi.jumi.core.api.*;
import fi.jumi.core.config.*;
import fi.jumi.core.events.suiteListener.*;
import fi.jumi.core.ipc.api.RequestListener;
import fi.jumi.core.network.*;
import fi.jumi.core.util.Boilerplate;
import fi.jumi.core.util.timeout.InitialMessageTimeout;
import fi.jumi.launcher.daemon.Steward;
import fi.jumi.launcher.process.*;
import org.apache.commons.io.IOUtils;

import javax.annotation.WillClose;
import javax.annotation.concurrent.*;
import java.io.*;
import java.nio.file.Paths;
import java.util.concurrent.*;

@NotThreadSafe
public class ProcessStartingDaemonSummoner implements DaemonSummoner {

    private static final DynamicEventizer<DaemonListener> eventizer = new DynamicEventizer<>(DaemonListener.class);

    private final Steward steward;
    private final ProcessStarter processStarter;
    private final NetworkServer daemonConnector;

    private final OutputStream outputListener; // TODO: remove me

    public ProcessStartingDaemonSummoner(Steward steward, ProcessStarter processStarter,
            NetworkServer daemonConnector, @WillClose OutputStream outputListener) {
        this.steward = steward;
        this.processStarter = processStarter;
        this.daemonConnector = daemonConnector;
        this.outputListener = outputListener;
    }

    @Override
    public void connectToDaemon(SuiteConfiguration suite, DaemonConfiguration daemon,
            ActorRef<DaemonListener> listener) {
        // XXX: should we handle multiple connections properly, even though we are expecting only one?
        int port = daemonConnector.listenOnAnyPort(new OneTimeDaemonListenerFactory(
                withInitialMessageTimeout(listener.tell(), daemon.getStartupTimeout())));
        daemon = daemon.melt().setDaemonDir(steward.createDaemonDir(daemon.getJumiHome())).setLauncherPort(port)
                .freeze();

        try {
            JvmArgs jvmArgs = new JvmArgsBuilder().setExecutableJar(steward.getDaemonJar(daemon.getJumiHome()))
                    .setWorkingDir(Paths.get(suite.getWorkingDirectory())).setJvmOptions(suite.getJvmOptions())
                    .setSystemProperties(daemon.toSystemProperties()).setProgramArgs(daemon.toProgramArgs())
                    .freeze();
            Process process = processStarter.startJavaProcess(jvmArgs);
            copyInBackground(process.getInputStream(), outputListener); // TODO: write the output to a log file using OS pipes, read it from there with AppRunner
        } catch (Exception e) {
            throw Boilerplate.rethrow(e);
        }
    }

    private static DaemonListener withInitialMessageTimeout(DaemonListener listener, long timeoutMillis) {
        return eventizer.newFrontend(new InitialMessageTimeout<>(eventizer.newBackend(listener),
                getTimeoutMessages(timeoutMillis), timeoutMillis, TimeUnit.MILLISECONDS));
    }

    private static MessageReceiver<Event<DaemonListener>> getTimeoutMessages(long timeoutMillis) {
        MessageQueue<Event<DaemonListener>> timeoutMessages = new MessageQueue<>();
        DaemonListener listener = eventizer.newFrontend(timeoutMessages);
        listener.onMessage(new OnSuiteStartedEvent());
        listener.onMessage(new OnInternalErrorEvent("Failed to start the test runner daemon process",
                StackTrace.from(new RuntimeException(
                        "Could not connect to the daemon: timed out after " + timeoutMillis + " ms"))));
        listener.onMessage(new OnSuiteFinishedEvent());
        return timeoutMessages;
    }

    private static void copyInBackground(@WillClose InputStream src, @WillClose OutputStream dest) {
        // TODO: after removing me, update also ReleasingResourcesTest
        Thread t = new Thread(() -> {
            try {
                IOUtils.copy(src, dest);
            } catch (IOException e) {
                throw Boilerplate.rethrow(e);
            } finally {
                IOUtils.closeQuietly(src);
                IOUtils.closeQuietly(dest);
            }
        }, "Daemon Output Copier");
        t.setDaemon(true);
        t.start();
    }

    @ThreadSafe
    private static class OneTimeDaemonListenerFactory
            implements NetworkEndpointFactory<Event<SuiteListener>, Event<RequestListener>> {

        private final BlockingQueue<DaemonListener> oneTimeListener = new ArrayBlockingQueue<>(1);

        public OneTimeDaemonListenerFactory(DaemonListener listener) {
            this.oneTimeListener.add(listener);
        }

        @Override
        public NetworkEndpoint<Event<SuiteListener>, Event<RequestListener>> createEndpoint() {
            DaemonListener listener = oneTimeListener.poll();
            if (listener == null) {
                throw new IllegalStateException("already connected once");
            }
            return listener;
        }
    }
}