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

org.jboss.as.arquillian.container.managed.AppClientWrapper Maven / Gradle / Ivy

There is a newer version: 5.1.0.Beta5
Show newest version
/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.arquillian.container.managed;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.jboss.as.arquillian.container.ParameterUtils;
import org.jboss.logging.Logger;
import org.wildfly.plugin.tools.server.ServerManager;

/**
 * A wrapper for an application client process. Allows interacting with the application client process.
 *
 * @author Dominik Pospisil 
 * @author Stuart Douglas
 * @author James R. Perkins
 */
public class AppClientWrapper implements AutoCloseable {
    private final BlockingQueue outputQueue = new LinkedBlockingQueue<>();
    private final ManagedContainerConfiguration config;
    private final Logger log;
    private final Lock lock;
    private Process process;
    private ExecutorService executorService;

    private Future stdoutConsumer;
    private Future stderrConsumer;

    /**
     * Creates a new application client wrapper.
     *
     * @param config the configuration for the container
     * @param log    the logger to use
     */
    protected AppClientWrapper(final ManagedContainerConfiguration config, final Logger log) {
        this.config = config;
        this.log = log;
        lock = new ReentrantLock();
    }

    /**
     * If the application client has started, causes the current thread to wait, if necessary, until the application
     * client terminates or the specified wait has been reached.
     *
     * 

* If the application client has not started a value of {@code false} is returned and an error message logged * indicating the application client has not started. *

* *

* If the application client has started and the process has terminated, this method returns {@code true} * immediately. *

* * @param timeout the maximum time to wait * @param unit the time unit of the {@code timeout} argument * * @return {@code true} if the application client process has exited and {@code false} if the waiting time elapsed * before the process has exited * * @throws InterruptedException if the current thread is interrupted while waiting * @throws NullPointerException if unit is null * @see Process#waitFor(long, TimeUnit) */ @SuppressWarnings("UnusedReturnValue") public boolean waitForExit(final long timeout, final TimeUnit unit) throws InterruptedException { try { lock.lock(); if (process != null) { try { final boolean b = process.waitFor(timeout, unit); process = null; return b; } finally { close(); } } } finally { lock.unlock(); } log.warn("waitForExit was invoked before the process was started."); return false; } /** * Consumes all available output from application client using the output queue filled by the process * standard out reader thread. * * @param timeout number of milliseconds to wait for each subsequent line * * @return list of application client output lines */ public List readAll(final long timeout) { final List lines = new ArrayList<>(); String line = null; do { try { line = outputQueue.poll(timeout, TimeUnit.MILLISECONDS); if (line != null) lines.add(line); } catch (InterruptedException ignore) { } } while (line != null); return List.copyOf(lines); } /** * Starts the application client in a new process and creates two thread to read the process output ({@code stdout}) * and error streams ({@code stderr}). * * @throws IOException if there is a failure to start the application client process */ public void run() throws IOException { try { lock.lock(); if (process == null) { process = new ProcessBuilder(getAppClientCommand()) .start(); executorService = Executors.newFixedThreadPool(2); stdoutConsumer = executorService .submit(new LogConsumer(outputQueue, process.getInputStream(), Logger.Level.INFO, process.pid())); stderrConsumer = executorService .submit(new LogConsumer(null, process.getErrorStream(), Logger.Level.ERROR, process.pid())); } } finally { lock.unlock(); } } /** * Kills the application client. */ @Override public void close() { try { lock.lock(); if (process != null) { process.destroy(); try { process.waitFor(); } catch (InterruptedException e) { throw new RuntimeException(e); } } stdoutConsumer.cancel(true); stderrConsumer.cancel(true); executorService.shutdownNow(); } finally { lock.unlock(); } } private List getAppClientCommand() { final List cmd = new ArrayList<>(); final String archivePath = config.getClientAppEar(); final String clientArchiveName = config.getClientArchiveName(); final String jbossHome = config.getJbossHome(); if (jbossHome == null) throw new IllegalArgumentException("jbossHome config property is not set."); if (!ServerManager.isValidHomeDirectory(jbossHome)) throw new IllegalArgumentException("Server directory from config jbossHome doesn't exist: " + jbossHome); final String archiveArg = String.format("%s#%s", archivePath, clientArchiveName); final String client = config.resolveAppClientCommand(); final Path clientExe = Path.of(jbossHome, "bin", client); if (Files.notExists(clientExe)) { throw new IllegalArgumentException("Could not find appclient executable " + clientExe); } cmd.add(clientExe.toString()); cmd.add(archiveArg); if (config.getClientArguments() != null) { cmd.addAll(ParameterUtils.splitParams(config.getClientArguments())); } log.info("AppClient cmd: " + cmd); return cmd; } private class LogConsumer implements Runnable { private final BlockingQueue queue; private final InputStreamReader reader; private final Logger.Level level; private final long pid; private LogConsumer(final BlockingQueue queue, final InputStream in, final Logger.Level level, final long pid) { this.queue = queue; this.reader = new InputStreamReader(in, StandardCharsets.UTF_8); this.level = level; this.pid = pid; } @Override public void run() { final StringBuilder buffer = new StringBuilder(); final char[] inBuffer = new char[256]; int len; try { while ((len = reader.read(inBuffer)) != -1) { int mark = 0; int i; for (i = 0; i < len; i++) { final char c = inBuffer[i]; if (c == '\n') { buffer.append(inBuffer, mark, i - mark); log.log(level, buffer.toString()); if (queue != null) { queue.add(buffer.toString()); } buffer.setLength(0); mark = i + 1; } } buffer.append(inBuffer, mark, i - mark); } // If we're here, we should log the buffer if it's not empty if (buffer.length() > 0) { log.log(level, buffer.toString()); if (queue != null) { queue.add(buffer.toString()); } } } catch (IOException e) { if (buffer.length() > 0) { log.errorf(e, "Failed to consume output from %s: %s", pid, buffer.toString()); buffer.setLength(0); } else { log.errorf(e, "Failed to consume output from %s", pid); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy