io.fabric8.spi.process.AbstractProcessHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.fabric8.spi.process.AbstractProcessHandler.java

Source

/*
 * #%L
 * Fabric8 :: SPI
 * %%
 * Copyright (C) 2014 Red Hat
 * %%
 * Licensed 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.
 * #L%
 */

package io.fabric8.spi.process;

import static io.fabric8.spi.Agent.NOTIFICATION_TYPE_AGENT_REGISTRATION;
import io.fabric8.api.ContainerAttributes;
import io.fabric8.api.process.ProcessOptions;
import io.fabric8.spi.Agent;
import io.fabric8.spi.AgentRegistration;
import io.fabric8.spi.process.ManagedProcess.State;
import io.fabric8.spi.utils.HostUtils;
import io.fabric8.spi.utils.ManagementUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnector;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.jboss.gravia.repository.DefaultMavenDelegateRepository;
import org.jboss.gravia.repository.MavenDelegateRepository;
import org.jboss.gravia.resource.MavenCoordinates;
import org.jboss.gravia.resource.Resource;
import org.jboss.gravia.resource.ResourceContent;
import org.jboss.gravia.runtime.LifecycleException;
import org.jboss.gravia.runtime.spi.PropertiesProvider;
import org.jboss.gravia.utils.IOUtils;
import org.jboss.gravia.utils.IllegalArgumentAssertion;
import org.jboss.gravia.utils.IllegalStateAssertion;

/**
 * The managed root container
 *
 * @author thomas.diesler@jboss.com
 * @since 26-Feb-2014
 */
public abstract class AbstractProcessHandler implements ProcessHandler {

    private final MavenDelegateRepository mavenRepository;
    private MutableManagedProcess managedProcess;
    private Process process;

    protected AbstractProcessHandler(PropertiesProvider propsProvider) {
        IllegalArgumentAssertion.assertNotNull(propsProvider, "propsProvider");
        this.mavenRepository = new DefaultMavenDelegateRepository(propsProvider);
    }

    @Override
    public final ManagedProcess create(AgentRegistration agentReg, ProcessOptions options,
            ProcessIdentity identity) {

        File targetDir = options.getTargetPath().toAbsolutePath().toFile();
        IllegalStateAssertion.assertTrue(targetDir.isDirectory() || targetDir.mkdirs(),
                "Cannot create target dir: " + targetDir);

        File homeDir = null;
        for (MavenCoordinates artefact : options.getMavenCoordinates()) {
            Resource resource = mavenRepository.findMavenResource(artefact);
            IllegalStateAssertion.assertNotNull(resource, "Cannot find maven resource: " + artefact);

            ResourceContent content = resource.adapt(ResourceContent.class);
            IllegalStateAssertion.assertNotNull(content, "Cannot obtain resource content for: " + artefact);

            try {
                ArchiveInputStream ais;
                if ("tar.gz".equals(artefact.getType())) {
                    InputStream inputStream = content.getContent();
                    ais = new TarArchiveInputStream(new GZIPInputStream(inputStream));
                } else {
                    InputStream inputStream = content.getContent();
                    ais = new ArchiveStreamFactory().createArchiveInputStream(artefact.getType(), inputStream);
                }
                ArchiveEntry entry = null;
                boolean needContainerHome = homeDir == null;
                while ((entry = ais.getNextEntry()) != null) {
                    File targetFile;
                    if (needContainerHome) {
                        targetFile = new File(targetDir, entry.getName());
                    } else {
                        targetFile = new File(homeDir, entry.getName());
                    }
                    if (!entry.isDirectory()) {
                        File parentDir = targetFile.getParentFile();
                        IllegalStateAssertion.assertTrue(parentDir.exists() || parentDir.mkdirs(),
                                "Cannot create target directory: " + parentDir);

                        FileOutputStream fos = new FileOutputStream(targetFile);
                        copyStream(ais, fos);
                        fos.close();

                        if (needContainerHome && homeDir == null) {
                            File currentDir = parentDir;
                            while (!currentDir.getParentFile().equals(targetDir)) {
                                currentDir = currentDir.getParentFile();
                            }
                            homeDir = currentDir;
                        }
                    }
                }
                ais.close();
            } catch (RuntimeException rte) {
                throw rte;
            } catch (Exception ex) {
                throw new IllegalStateException("Cannot extract artefact: " + artefact, ex);
            }
        }

        managedProcess = new DefaultManagedProcess(identity, options, homeDir.toPath(), State.CREATED);
        managedProcess.addAttribute(ManagedProcess.ATTRIBUTE_KEY_AGENT_REGISTRATION, agentReg);
        managedProcess.addAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_SERVER_URL,
                agentReg.getJmxServerUrl());
        managedProcess.addAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_USERNAME,
                agentReg.getJmxUsername());
        managedProcess.addAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_PASSWORD,
                agentReg.getJmxPassword());
        try {
            doConfigure(managedProcess);
        } catch (Exception ex) {
            throw new LifecycleException("Cannot configure container", ex);
        }
        return new ImmutableManagedProcess(managedProcess);
    }

    @Override
    public final Future<ManagedProcess> start() {
        State state = managedProcess.getState();
        assertNotDestroyed(state);

        // Setup a call back notification to get the JMX connection of the started process
        final CountDownLatch latch = new CountDownLatch(1);
        String jmxAgentServiceURL = managedProcess
                .getAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_SERVER_URL);
        String jmxAgentUsername = managedProcess.getAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_USERNAME);
        String jmxAgentPassword = managedProcess.getAttribute(ContainerAttributes.ATTRIBUTE_KEY_AGENT_JMX_PASSWORD);
        JMXConnector connector = ManagementUtils.getJMXConnector(jmxAgentServiceURL, jmxAgentUsername,
                jmxAgentPassword, 200, TimeUnit.MILLISECONDS);
        try {
            final MBeanServerConnection server = connector.getMBeanServerConnection();
            server.addNotificationListener(Agent.OBJECT_NAME, new NotificationListener() {
                @Override
                public void handleNotification(Notification notification, Object handback) {
                    String eventType = notification.getType();
                    if (NOTIFICATION_TYPE_AGENT_REGISTRATION.equals(eventType)) {
                        AgentRegistration agentReg = (AgentRegistration) notification.getSource();
                        String agentName = agentReg.getIdentity().getName();
                        String procName = (String) handback;
                        if (agentName.equals(procName)) {
                            try {
                                server.removeNotificationListener(Agent.OBJECT_NAME, this);
                            } catch (Exception ex) {
                                // ignore
                            }
                            latch.countDown();
                        }
                    }
                }
            }, null, managedProcess.getIdentity().getName());
        } catch (RuntimeException rte) {
            throw rte;
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        } finally {
            IOUtils.safeClose(connector);
        }

        try {
            if (state == State.CREATED || state == State.STOPPED) {
                doStart(managedProcess);
                IllegalStateAssertion.assertNotNull(process, "No process created");
                managedProcess.setState(State.STARTED);
            }
        } catch (Exception ex) {
            throw new LifecycleException("Cannot start container", ex);
        }

        return new ProcessFuture(managedProcess); //, latch);
    }

    @Override
    public final Future<ManagedProcess> stop() {
        State state = managedProcess.getState();
        assertNotDestroyed(state);
        try {
            if (state == State.STARTED) {
                doStop(managedProcess);
                managedProcess.setState(State.STOPPED);
                destroyProcess();
            }
        } catch (Exception ex) {
            throw new LifecycleException("Cannot stop container", ex);
        }
        return new ProcessFuture(managedProcess);
    }

    @Override
    public final ManagedProcess destroy() {
        State state = managedProcess.getState();
        assertNotDestroyed(state);
        if (state == State.STARTED) {
            try {
                stop();
            } catch (Exception ex) {
                // ignore
            }
        }
        try {
            doDestroy(managedProcess);
        } catch (Exception ex) {
            throw new LifecycleException("Cannot destroy container", ex);
        } finally {
            managedProcess.setState(State.DESTROYED);
        }
        return new ImmutableManagedProcess(managedProcess);
    }

    private void assertNotDestroyed(State state) {
        IllegalStateAssertion.assertFalse(state == State.DESTROYED, "Cannot start container in state: " + state);
    }

    protected void doConfigure(MutableManagedProcess process) throws Exception {
    }

    protected void doStart(MutableManagedProcess process) throws Exception {
    }

    protected void doStop(MutableManagedProcess process) throws Exception {
    }

    protected void doDestroy(MutableManagedProcess process) throws Exception {
    }

    protected void startProcess(ProcessBuilder processBuilder, ProcessOptions options) throws IOException {
        process = processBuilder.start();
        new Thread(new ConsoleConsumer(process, options)).start();
    }

    protected void destroyProcess() throws Exception {
        if (process != null) {
            process.destroy();
            process.waitFor();
        }
    }

    protected final int nextAvailablePort(int portValue) {
        return nextAvailablePort(portValue, null);
    }

    protected int nextAvailablePort(int portValue, InetAddress bindAddr) {
        return HostUtils.nextAvailablePort(portValue, bindAddr);
    }

    // [TODO] call IOUtils.copyStream()
    private static long copyStream(InputStream input, OutputStream output) throws IOException {
        return copyStream(input, output, 8024);
    }

    private static long copyStream(InputStream input, OutputStream output, int buffersize) throws IOException {
        final byte[] buffer = new byte[buffersize];
        int n = 0;
        long count = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
            count += n;
        }
        return count;
    }

    /**
     * Runnable that consumes the output of the process.
     * If nothing consumes the output the container may hang on some platforms
     */
    public static class ConsoleConsumer implements Runnable {

        private final Process process;
        private final ProcessOptions options;

        public ConsoleConsumer(Process process, ProcessOptions options) {
            this.process = process;
            this.options = options;
        }

        @Override
        public void run() {
            final InputStream stream = process.getInputStream();
            try {
                byte[] buf = new byte[32];
                int num;
                // Do not try reading a line cos it considers '\r' end of line
                while ((num = stream.read(buf)) != -1) {
                    if (options.isOutputToConsole())
                        System.out.write(buf, 0, num);
                }
            } catch (IOException e) {
            }
        }
    }
}