Move client classes into a separate module

This commit is contained in:
Peter Palaga
2020-05-29 15:42:01 +02:00
parent 8d70da739e
commit 4f2c0fb215
49 changed files with 354 additions and 246 deletions

62
client/pom.xml Normal file
View File

@@ -0,0 +1,62 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jboss.fuse.mvnd</groupId>
<artifactId>mvnd</artifactId>
<version>0.1-SNAPSHOT</version>
</parent>
<artifactId>mvnd-client</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Client</name>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal</artifactId>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jansi</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>io.takari.maven.plugins</groupId>
<artifactId>takari-lifecycle-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>sisu-index</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2018 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.nio.Buffer;
public class BufferCaster {
/**
* Without this cast, when the code compiled by Java 9+ is executed on Java 8, it will throw
* java.lang.NoSuchMethodError: Method flip()Ljava/nio/ByteBuffer; does not exist in class java.nio.ByteBuffer
*/
@SuppressWarnings("RedundantCast")
public static <T extends Buffer> Buffer cast(T byteBuffer) {
return (Buffer) byteBuffer;
}
}

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.fusesource.jansi.Ansi;
import org.jboss.fuse.mvnd.client.ClientOutput.TerminalOutput;
import org.jboss.fuse.mvnd.client.Message.BuildEvent;
import org.jboss.fuse.mvnd.client.Message.BuildException;
import org.jboss.fuse.mvnd.client.Message.BuildMessage;
import org.jboss.fuse.mvnd.client.Message.MessageSerializer;
import org.jboss.fuse.mvnd.jpm.Process;
import org.jboss.fuse.mvnd.jpm.ProcessImpl;
import org.jboss.fuse.mvnd.jpm.ScriptUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Client {
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
public static final String DAEMON_DEBUG = "daemon.debug";
public static final String DAEMON_IDLE_TIMEOUT = "daemon.idleTimeout";
public static final int DEFAULT_IDLE_TIMEOUT = (int) TimeUnit.HOURS.toMillis(3);
public static final int DEFAULT_PERIODIC_CHECK_INTERVAL_MILLIS = 10 * 1000;
public static final int CANCEL_TIMEOUT = 10 * 1000;
private final Layout layout;
private final Optional<ClientLayout> clientLayout;
public static void main(String[] argv) throws Exception {
final List<String> args = new ArrayList<>(Arrays.asList(argv));
Path logFile = null;
for (int i = 0; i < args.size() - 2; i++) {
String arg = args.get(i);
if ("-l".equals(arg) || "--log-file".equals(arg)) {
logFile = Paths.get(args.get(i + 1));
args.remove(i);
args.remove(i);
break;
}
}
try (TerminalOutput output = new TerminalOutput(logFile)) {
new Client(Layout.getEnvInstance(), Optional.empty()).execute(output, args);
}
}
public Client(Layout layout, Optional<ClientLayout> clientLayout) {
this.layout = layout;
this.clientLayout = clientLayout;
}
public <O extends ClientOutput> ClientResult<O> execute(O output, String... argv) throws IOException {
return execute(output, Arrays.asList(argv));
}
public <O extends ClientOutput> ClientResult<O> execute(O output, List<String> argv) throws IOException {
output.debug("Starting client");
final List<String> args = new ArrayList<>(argv);
// Print version if needed
boolean version = args.contains("-v") || args.contains("-version") || args.contains("--version");
boolean showVersion = args.contains("-V") || args.contains("--show-version");
boolean debug = args.contains("-X") || args.contains("--debug");
if (version || showVersion || debug) {
Properties props = new Properties();
try (InputStream is = Client.class.getResourceAsStream("build.properties")) {
props.load(is);
}
String v = Ansi.ansi().bold().a("Maven Daemon " + props.getProperty("version")).reset().toString();
output.log(v);
/* Do not return, rather pass -v to the server so that the client module does not need to depend on any Maven artifacts */
}
final Path javaHome = layout.javaHome();
try (DaemonRegistry registry = new DaemonRegistry(layout.registry())) {
boolean status = args.remove("--status");
if (status) {
output.log(String.format(" %36s %5s %5s %7s %s",
"UUID", "PID", "Port", "Status", "Timestamp"));
registry.getAll().forEach(d -> output.log(String.format(" %36s %5s %5s %7s %s",
d.getUid(), d.getPid(), d.getAddress(), d.getState(),
new Date(Math.max(d.getLastIdle(), d.getLastBusy())).toString())));
return new ClientResult<O>(argv, true, output);
}
boolean stop = args.remove("--stop");
if (stop) {
DaemonInfo[] dis = registry.getAll().toArray(new DaemonInfo[0]);
if (dis.length > 0) {
output.log("Stopping " + dis.length + " running daemons");
for (DaemonInfo di : dis) {
try {
new ProcessImpl(di.getPid()).destroy();
} catch (IOException t) {
System.out.println("Daemon " + di.getUid() + ": " + t.getMessage());
} catch (Exception t) {
System.out.println("Daemon " + di.getUid() + ": " + t);
} finally {
registry.remove(di.getUid());
}
}
}
return new ClientResult<O>(argv, true, output);
}
setDefaultArgs(args);
clientLayout.ifPresent(cl -> clientLayout(cl, args));
DaemonConnector connector = new DaemonConnector(layout, registry, this::startDaemon, new MessageSerializer());
List<String> opts = new ArrayList<>();
DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome.toString(), opts));
daemon.dispatch(new Message.BuildRequest(
args,
layout.userDir().toString(),
layout.multiModuleProjectDirectory().toString()));
while (true) {
Message m = daemon.receive();
if (m instanceof BuildException) {
output.error((BuildException) m);
return new ClientResult<O>(argv, false, output);
} else if (m instanceof BuildEvent) {
BuildEvent be = (BuildEvent) m;
switch (be.getType()) {
case BuildStarted:
break;
case BuildStopped:
return new ClientResult<O>(argv, true, output);
case ProjectStarted:
case MojoStarted:
case MojoStopped:
output.projectStateChanged(be.projectId, be.display);
break;
case ProjectStopped:
output.projectFinished(be.projectId);
}
} else if (m instanceof BuildMessage) {
BuildMessage bm = (BuildMessage) m;
output.log(bm.getMessage());
}
}
}
}
static void clientLayout(ClientLayout cl, List<String> args) {
if (!args.stream().anyMatch(arg -> arg.equals("-s") || arg.equals("--settings"))) {
args.add("-s");
args.add(cl.getSettings().toString());
}
if (!args.stream().anyMatch(arg -> arg.startsWith("-Dmaven.repo.local"))) {
args.add("-Dmaven.repo.local=" + cl.getLocalMavenRepository().toString());
}
}
static void setDefaultArgs(List<String> args) {
if (!args.stream().anyMatch(arg -> arg.startsWith("-T") || arg.equals("--threads"))) {
args.add("-T1C");
}
if (!args.stream().anyMatch(arg -> arg.startsWith("-b") || arg.equals("--builder"))) {
args.add("-bsmart");
}
}
String startDaemon() {
// DaemonParameters parms = new DaemonParameters();
// for (String arg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
//
// }
// List<String> args = new ArrayList<>();
// args.add(javaHome.resolve(java).toString());
// args.addAll(parms.getEffectiveJvmArgs());
// args.add("-cp");
// args.add(classpath);
String uid = UUID.randomUUID().toString();
Path mavenHome = layout.mavenHome();
Path javaHome = layout.javaHome();
Path workingDir = layout.userDir();
String command = "";
try {
String url = Client.class.getClassLoader().getResource(Client.class.getName().replace('.', '/') + ".class").toString();
String classpath = url.substring("file:jar:".length(), url.indexOf('!'));
String java = ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java";
List<String> args = new ArrayList<>();
args.add("\"" + javaHome.resolve(java) + "\"");
args.add("-classpath");
args.add("\"" + classpath + "\"");
if (Boolean.getBoolean(DAEMON_DEBUG)) {
args.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000");
}
args.add("-Dmaven.home=\"" + mavenHome + "\"");
args.add("-Dlogback.configurationFile=logback.xml");
args.add("-Ddaemon.uid=" + uid);
args.add("-Xmx4g");
final String timeout = System.getProperty(DAEMON_IDLE_TIMEOUT);
if (timeout != null) {
args.add("-D" + DAEMON_IDLE_TIMEOUT + "=" + timeout);
}
args.add("\"-Dmaven.multiModuleProjectDirectory=" + layout.multiModuleProjectDirectory().toString() + "\"");
args.add(ServerMain.class.getName());
command = String.join(" ", args);
LOGGER.debug("Starting daemon process: uid = {}, workingDir = {}, daemonArgs: {}", uid, workingDir, command);
Process.create(workingDir.toFile(), command);
return uid;
} catch (Exception e) {
throw new DaemonException.StartException(
String.format("Error starting daemon: uid = %s, workingDir = %s, daemonArgs: %s",
uid, workingDir, command), e);
}
}
}

View File

@@ -0,0 +1,28 @@
package org.jboss.fuse.mvnd.client;
import java.nio.file.Path;
import java.util.Objects;
/**
* Local paths relevant for the {@link Client}.
*/
public class ClientLayout {
private final Path localMavenRepository;
private final Path settings;
public ClientLayout(Path localMavenRepository, Path settings) {
super();
this.localMavenRepository = Objects.requireNonNull(localMavenRepository, "localMavenRepository");
this.settings = Objects.requireNonNull(settings, "settings");
}
public Path getLocalMavenRepository() {
return localMavenRepository;
}
public Path getSettings() {
return settings;
}
}

View File

@@ -0,0 +1,191 @@
package org.jboss.fuse.mvnd.client;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.function.Consumer;
import org.jboss.fuse.mvnd.client.Message.BuildException;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStyle;
import org.jline.utils.Display;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A sink for various kinds of events sent by the daemon.
*/
public interface ClientOutput extends AutoCloseable {
public void projectStateChanged(String projectId, String display);
public void projectFinished(String projectId);
public void log(String message);
public void error(BuildException m);
public void debug(String string);
/**
* A terminal {@link ClientOutput} based on JLine.
*/
static class TerminalOutput implements ClientOutput {
private static final Logger LOGGER = LoggerFactory.getLogger(TerminalOutput.class);
private final Terminal terminal;
private final Display display;
private final LinkedHashMap<String, String> projects = new LinkedHashMap<>();
private long lastUpdate = 0;
private final Log log;
public TerminalOutput(Path logFile) throws IOException {
this.terminal = TerminalBuilder.terminal();
this.display = new Display(terminal, false);
this.log = logFile == null ? new ClientOutput.Log.MessageCollector(terminal)
: new ClientOutput.Log.FileLog(logFile);
}
public void projectStateChanged(String projectId, String task) {
projects.put(projectId, task);
update();
}
private void update() {
// no need to refresh the display at every single step
long curTime = System.currentTimeMillis();
if (curTime - lastUpdate >= 10) {
Size size = terminal.getSize();
display.resize(size.getRows(), size.getColumns());
List<AttributedString> lines = new ArrayList<>();
projects.values().stream()
.map(AttributedString::fromAnsi)
.map(s -> s.columnSubSequence(0, size.getColumns() - 1))
.forEachOrdered(lines::add);
// Make sure we don't try to display more lines than the terminal height
int rem = 0;
while (lines.size() >= terminal.getHeight()) {
lines.remove(0);
rem++;
}
lines.add(0, new AttributedString("Building..." + (rem > 0 ? " (" + rem + " more)" : "")));
display.update(lines, -1);
lastUpdate = curTime;
}
}
public void projectFinished(String projectId) {
projects.remove(projectId);
update();
}
@Override
public void log(String message) {
log.accept(message);
}
@Override
public void close() throws Exception {
display.update(Collections.emptyList(), 0);
LOGGER.debug("Done receiving, printing log");
log.close();
LOGGER.debug("Done !");
terminal.flush();
}
@Override
public void error(BuildException error) {
display.update(Collections.emptyList(), 0);
final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
final String msg;
if ("org.apache.commons.cli.UnrecognizedOptionException".equals(error.getClassName())) {
msg = "Unable to parse command line options: " + error.getMessage();
} else {
msg = error.getClassName() + ": " + error.getMessage();
}
terminal.writer().println(new AttributedString(msg, s).toAnsi());
terminal.flush();
}
@Override
public void debug(String msg) {
LOGGER.debug(msg);
}
}
/**
* A closeable string message consumer.
*/
interface Log extends Consumer<String>, AutoCloseable {
/**
* A {@link Log} backed by a file.
*/
public static class FileLog implements Log {
private final Writer out;
private Path logFile;
public FileLog(Path logFile) throws IOException {
super();
this.out = Files.newBufferedWriter(logFile, StandardCharsets.UTF_8);
this.logFile = logFile;
}
@Override
public void accept(String message) {
try {
out.write(message);
out.write('\n');
} catch (IOException e) {
throw new RuntimeException("Could not write to " + logFile, e);
}
}
@Override
public void close() throws IOException {
out.close();
}
}
/**
* A {@link Log} that first collects all incoming messages in a {@link List} and outputs them to a JLine
* {@link Terminal} upon {@link #close()}.
*/
public static class MessageCollector implements Log {
private final List<String> messages = new ArrayList<>();
private final Terminal terminal;
public MessageCollector(Terminal terminal) {
super();
this.terminal = terminal;
}
@Override
public void accept(String message) {
messages.add(message);
}
@Override
public void close() {
messages.forEach(terminal.writer()::println);
terminal.flush();
}
}
}
}

View File

@@ -0,0 +1,54 @@
package org.jboss.fuse.mvnd.client;
import java.util.ArrayList;
import java.util.List;
/**
* A result of a {@code mvnd} build.
*
* @param <O> the type of the {@link ClientOutput}.
*/
public class ClientResult<O extends ClientOutput> {
private final boolean success;
private final O clientOutput;
private final List<String> args;
public ClientResult(List<String> args, boolean success, O clientOutput) {
super();
this.args = new ArrayList<>(args);
this.success = success;
this.clientOutput = clientOutput;
}
public ClientResult<O> assertSuccess() {
if (!this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build failed: ")));
}
return this;
}
public ClientResult<O> assertFailure() {
if (this.success) {
throw new AssertionError(appendCommand(new StringBuilder("Build did not fail: ")));
}
return this;
}
public O getClientOutput() {
return clientOutput;
}
public boolean isSuccess() {
return success;
}
StringBuilder appendCommand(StringBuilder sb) {
sb.append("mvnd");
for (String arg : args) {
sb.append(" \"").append(arg).append('"');
}
return sb;
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DaemonClientConnection {
private final static Logger LOG = LoggerFactory.getLogger(DaemonClientConnection.class);
private final DaemonConnection<Message> connection;
private final DaemonInfo daemon;
private final StaleAddressDetector staleAddressDetector;
private boolean hasReceived;
private final Lock dispatchLock = new ReentrantLock();
public DaemonClientConnection(DaemonConnection<Message> connection, DaemonInfo daemon, StaleAddressDetector staleAddressDetector) {
this.connection = connection;
this.daemon = daemon;
this.staleAddressDetector = staleAddressDetector;
}
public DaemonInfo getDaemon() {
return daemon;
}
public void dispatch(Message message) throws DaemonException.ConnectException {
LOG.debug("thread {}: dispatching {}", Thread.currentThread().getId(), message.getClass());
try {
dispatchLock.lock();
try {
connection.dispatch(message);
connection.flush();
} finally {
dispatchLock.unlock();
}
} catch (DaemonException.MessageIOException e) {
LOG.debug("Problem dispatching message to the daemon. Performing 'on failure' operation...");
if (!hasReceived && staleAddressDetector.maybeStaleAddress(e)) {
throw new DaemonException.StaleAddressException("Could not dispatch a message to the daemon.", e);
}
throw new DaemonException.ConnectException("Could not dispatch a message to the daemon.", e);
}
}
public Message receive() throws DaemonException.ConnectException {
try {
return connection.receive();
} catch (DaemonException.MessageIOException e) {
LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation...");
if (!hasReceived && staleAddressDetector.maybeStaleAddress(e)) {
throw new DaemonException.StaleAddressException("Could not receive a message from the daemon.", e);
}
throw new DaemonException.ConnectException("Could not receive a message from the daemon.", e);
} finally {
hasReceived = true;
}
}
public void stop() {
LOG.debug("thread {}: connection stop", Thread.currentThread().getId());
connection.close();
}
interface StaleAddressDetector {
/**
* @return true if the failure should be considered due to a stale address.
*/
boolean maybeStaleAddress(Exception failure);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
public class DaemonCompatibilitySpec {
private final String javaHome;
private final List<String> options;
public DaemonCompatibilitySpec(String javaHome, List<String> options) {
this.javaHome = javaHome;
this.options = options;
}
public boolean isSatisfiedBy(DaemonInfo daemon) {
return whyUnsatisfied(daemon) == null;
}
public String whyUnsatisfied(DaemonInfo daemon) {
if (!javaHomeMatches(daemon)) {
return "Java home is different.\n" + description(daemon);
} else if (!daemonOptsMatch(daemon)) {
return "At least one daemon option is different.\n" + description(daemon);
}
return null;
}
private String description(DaemonInfo context) {
return "Wanted: " + this + "\n"
+ "Actual: " + context + "\n";
}
private boolean daemonOptsMatch(DaemonInfo daemon) {
return daemon.getOptions().containsAll(options)
&& daemon.getOptions().size() == options.size();
}
private boolean javaHomeMatches(DaemonInfo daemon) {
return Objects.equals(
Paths.get(daemon.getJavaHome()).normalize(),
Paths.get(javaHome).normalize());
}
@Override
public String toString() {
return "DaemonCompatibilitySpec{" +
"javaHome='" + javaHome + '\'' +
", daemonOpts=" + options +
'}';
}
}

View File

@@ -0,0 +1,302 @@
/*
* Copyright 2016 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DaemonConnection<T> implements AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonConnection.class);
private final SocketChannel socket;
private final Serializer<T> serializer;
private final InetSocketAddress localAddress;
private final InetSocketAddress remoteAddress;
private final DataInputStream instr;
private final DataOutputStream outstr;
public DaemonConnection(SocketChannel socket, Serializer<T> serializer) {
this.socket = socket;
this.serializer = serializer;
try {
// NOTE: we use non-blocking IO as there is no reliable way when using blocking IO to shutdown reads while
// keeping writes active. For example, Socket.shutdownInput() does not work on Windows.
socket.configureBlocking(false);
outstr = new DataOutputStream(new SocketOutputStream(socket));
instr = new DataInputStream(new SocketInputStream(socket));
} catch (IOException e) {
throw new DaemonException.InterruptedException(e);
}
localAddress = (InetSocketAddress) socket.socket().getLocalSocketAddress();
remoteAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
}
@Override
public String toString() {
return "socket connection from " + localAddress + " to " + remoteAddress;
}
public T receive() throws DaemonException.MessageIOException {
try {
return serializer.read(instr);
} catch (EOFException e) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Discarding EOFException: {}", e.toString());
}
return null;
} catch (ClassNotFoundException | IOException e) {
throw new DaemonException.RecoverableMessageIOException(String.format("Could not read message from '%s'.", remoteAddress), e);
} catch (Throwable e) {
throw new DaemonException.MessageIOException(String.format("Could not read message from '%s'.", remoteAddress), e);
}
}
private static boolean isEndOfStream(Exception e) {
if (e instanceof EOFException) {
return true;
}
if (e instanceof IOException) {
if (Objects.equals(e.getMessage(), "An existing connection was forcibly closed by the remote host")) {
return true;
}
if (Objects.equals(e.getMessage(), "An established connection was aborted by the software in your host machine")) {
return true;
}
if (Objects.equals(e.getMessage(), "Connection reset by peer")) {
return true;
}
if (Objects.equals(e.getMessage(), "Connection reset")) {
return true;
}
}
return false;
}
public void dispatch(T message) throws DaemonException.MessageIOException {
try {
serializer.write(outstr, message);
outstr.flush();
} catch (ClassNotFoundException | IOException e) {
throw new DaemonException.RecoverableMessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
} catch (Throwable e) {
throw new DaemonException.MessageIOException(String.format("Could not write message %s to '%s'.", message, remoteAddress), e);
}
}
public void flush() throws DaemonException.MessageIOException {
try {
outstr.flush();
} catch (Throwable e) {
throw new DaemonException.MessageIOException(String.format("Could not write '%s'.", remoteAddress), e);
}
}
public void close() {
Throwable failure = null;
List<Closeable> elements = Arrays.asList(this::flush, instr, outstr, socket);
for (Closeable element : elements) {
try {
element.close();
} catch (Throwable throwable) {
if (failure == null) {
failure = throwable;
} else if (!Thread.currentThread().isInterrupted()) {
LOGGER.error(String.format("Could not stop %s.", element), throwable);
}
}
}
if (failure != null) {
throw new DaemonException(failure);
}
}
private static class SocketInputStream extends InputStream {
private final Selector selector;
private final ByteBuffer buffer;
private final SocketChannel socket;
private final byte[] readBuffer = new byte[1];
public SocketInputStream(SocketChannel socket) throws IOException {
this.socket = socket;
selector = Selector.open();
socket.register(selector, SelectionKey.OP_READ);
buffer = ByteBuffer.allocateDirect(4096);
BufferCaster.cast(buffer).limit(0);
}
@Override
public int read() throws IOException {
int nread = read(readBuffer, 0, 1);
if (nread <= 0) {
return nread;
}
return readBuffer[0] & 0xFF;
}
@Override
public int read(byte[] dest, int offset, int max) throws IOException {
if (max == 0) {
return 0;
}
if (buffer.remaining() == 0) {
try {
selector.select();
} catch (ClosedSelectorException e) {
return -1;
}
if (!selector.isOpen()) {
return -1;
}
BufferCaster.cast(buffer).clear();
int nread;
try {
nread = socket.read(buffer);
} catch (IOException e) {
if (isEndOfStream(e)) {
BufferCaster.cast(buffer).position(0);
BufferCaster.cast(buffer).limit(0);
return -1;
}
throw e;
}
BufferCaster.cast(buffer).flip();
if (nread < 0) {
return -1;
}
}
int count = Math.min(buffer.remaining(), max);
buffer.get(dest, offset, count);
return count;
}
@Override
public void close() throws IOException {
selector.close();
}
}
private static class SocketOutputStream extends OutputStream {
private static final int RETRIES_WHEN_BUFFER_FULL = 2;
private Selector selector;
private final SocketChannel socket;
private final ByteBuffer buffer;
private final byte[] writeBuffer = new byte[1];
public SocketOutputStream(SocketChannel socket) throws IOException {
this.socket = socket;
buffer = ByteBuffer.allocateDirect(32 * 1024);
}
@Override
public void write(int b) throws IOException {
writeBuffer[0] = (byte) b;
write(writeBuffer);
}
@Override
public void write(byte[] src, int offset, int max) throws IOException {
int remaining = max;
int currentPos = offset;
while (remaining > 0) {
int count = Math.min(remaining, buffer.remaining());
if (count > 0) {
buffer.put(src, currentPos, count);
remaining -= count;
currentPos += count;
}
while (buffer.remaining() == 0) {
writeBufferToChannel();
}
}
}
@Override
public void flush() throws IOException {
while (buffer.position() > 0) {
writeBufferToChannel();
}
}
private void writeBufferToChannel() throws IOException {
BufferCaster.cast(buffer).flip();
int count = writeWithNonBlockingRetry();
if (count == 0) {
// buffer was still full after non-blocking retries, now block
waitForWriteBufferToDrain();
}
buffer.compact();
}
private int writeWithNonBlockingRetry() throws IOException {
int count = 0;
int retryCount = 0;
while (count == 0 && retryCount++ < RETRIES_WHEN_BUFFER_FULL) {
count = socket.write(buffer);
if (count < 0) {
throw new EOFException();
} else if (count == 0) {
// buffer was full, just call Thread.yield
Thread.yield();
}
}
return count;
}
private void waitForWriteBufferToDrain() throws IOException {
if (selector == null) {
selector = Selector.open();
}
SelectionKey key = socket.register(selector, SelectionKey.OP_WRITE);
// block until ready for write operations
selector.select();
// cancel OP_WRITE selection
key.cancel();
// complete cancelling key
selector.selectNow();
}
@Override
public void close() throws IOException {
if (selector != null) {
selector.close();
selector = null;
}
}
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.Thread.sleep;
import static org.jboss.fuse.mvnd.client.DaemonState.Canceled;
public class DaemonConnector {
public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
public static final int CANCELED_WAIT_TIMEOUT = 3000;
private static final int CONNECT_TIMEOUT = 10000;
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonConnector.class);
private final DaemonRegistry registry;
private final Layout layout;
private final DaemonStarter daemonStarter;
private final Serializer<Message> serializer;
public DaemonConnector(Layout layout, DaemonRegistry registry, DaemonStarter daemonStarter, Serializer<Message> serializer) {
this.layout = layout;
this.registry = registry;
this.daemonStarter = daemonStarter;
this.serializer = serializer;
}
public DaemonClientConnection maybeConnect(DaemonCompatibilitySpec constraint) {
return findConnection(getCompatibleDaemons(registry.getAll(), constraint));
}
public DaemonClientConnection maybeConnect(DaemonInfo daemon) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
} catch (DaemonException.ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Ignoring.", daemon, e);
}
return null;
}
public DaemonClientConnection connect(DaemonCompatibilitySpec constraint) {
Map<Boolean, List<DaemonInfo>> idleBusy = registry.getAll().stream()
.collect(Collectors.groupingBy(di -> di.getState() == DaemonState.Idle));
final Collection<DaemonInfo> idleDaemons = idleBusy.getOrDefault(true, Collections.emptyList());
final Collection<DaemonInfo> busyDaemons = idleBusy.getOrDefault(false, Collections.emptyList());
// Check to see if there are any compatible idle daemons
DaemonClientConnection connection = connectToIdleDaemon(idleDaemons, constraint);
if (connection != null) {
return connection;
}
// Check to see if there are any compatible canceled daemons and wait to see if one becomes idle
connection = connectToCanceledDaemon(busyDaemons, constraint);
if (connection != null) {
return connection;
}
// No compatible daemons available - start a new daemon
handleStopEvents(idleDaemons, busyDaemons);
return startDaemon(constraint);
}
private void handleStopEvents(Collection<DaemonInfo> idleDaemons, Collection<DaemonInfo> busyDaemons) {
final List<DaemonStopEvent> stopEvents = registry.getStopEvents();
// Clean up old stop events
long time = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
List<DaemonStopEvent> oldStopEvents = stopEvents.stream()
.filter(e -> e.getTimestamp() < time)
.collect(Collectors.toList());
registry.removeStopEvents(oldStopEvents);
final List<DaemonStopEvent> recentStopEvents = stopEvents.stream()
.filter(e -> e.getTimestamp() >= time)
.collect(Collectors.groupingBy(DaemonStopEvent::getUid,
Collectors.minBy(this::compare)))
.values()
.stream()
.map(Optional::get)
.collect(Collectors.toList());
for (DaemonStopEvent stopEvent : recentStopEvents) {
LOGGER.info("Previous Daemon ({}) stopped at {} {}",
stopEvent.getUid(), stopEvent.getTimestamp(), stopEvent.getReason());
}
LOGGER.info(generate(busyDaemons.size(), idleDaemons.size(), recentStopEvents.size()));
}
public static String generate(final int numBusy, final int numIncompatible, final int numStopped) {
final int totalUnavailableDaemons = numBusy + numIncompatible + numStopped;
if (totalUnavailableDaemons > 0) {
final List<String> reasons = new ArrayList<>();
if (numBusy > 0) {
reasons.add(numBusy + " busy");
}
if (numIncompatible > 0) {
reasons.add(numIncompatible + " incompatible");
}
if (numStopped > 0) {
reasons.add(numStopped + " stopped");
}
return "Starting a Maven Daemon, "
+ String.join(" and ", reasons) + " Daemon" + (totalUnavailableDaemons > 1 ? "s" : "")
+ " could not be reused, use --status for details";
} else {
return "Starting a Maven Daemon (subsequent builds will be faster)";
}
}
private int compare(DaemonStopEvent event1, DaemonStopEvent event2) {
if (event1.getStatus() != null && event2.getStatus() == null) {
return -1;
} else if (event1.getStatus() == null && event2.getStatus() != null) {
return 1;
} else if (event1.getStatus() != null && event2.getStatus() != null) {
return event2.getStatus().compareTo(event1.getStatus());
}
return 0;
}
private DaemonClientConnection connectToIdleDaemon(Collection<DaemonInfo> idleDaemons, DaemonCompatibilitySpec constraint) {
final List<DaemonInfo> compatibleIdleDaemons = getCompatibleDaemons(idleDaemons, constraint);
return findConnection(compatibleIdleDaemons);
}
private DaemonClientConnection connectToCanceledDaemon(Collection<DaemonInfo> busyDaemons, DaemonCompatibilitySpec constraint) {
DaemonClientConnection connection = null;
Map<Boolean, List<DaemonInfo>> canceledBusy = busyDaemons.stream()
.collect(Collectors.groupingBy(di -> di.getState() == Canceled));
final Collection<DaemonInfo> compatibleCanceledDaemons = getCompatibleDaemons(
canceledBusy.getOrDefault(true, Collections.emptyList()), constraint);
if (!compatibleCanceledDaemons.isEmpty()) {
LOGGER.info("Waiting for daemons with canceled builds to become available");
long start = System.currentTimeMillis();
while (connection == null && System.currentTimeMillis() - start < CANCELED_WAIT_TIMEOUT) {
try {
sleep(200);
connection = connectToIdleDaemon(registry.getIdle(), constraint);
} catch (InterruptedException e) {
throw new DaemonException.InterruptedException(e);
}
}
}
return connection;
}
private List<DaemonInfo> getCompatibleDaemons(Iterable<DaemonInfo> daemons, DaemonCompatibilitySpec constraint) {
List<DaemonInfo> compatibleDaemons = new LinkedList<>();
for (DaemonInfo daemon : daemons) {
if (constraint.isSatisfiedBy(daemon)) {
compatibleDaemons.add(daemon);
} else {
LOGGER.info("Found daemon {} however it does not match the desired criteria.\n"
+ constraint.whyUnsatisfied(daemon) + "\n"
+ " Looking for a different daemon...", daemon);
}
}
return compatibleDaemons;
}
private DaemonClientConnection findConnection(List<DaemonInfo> compatibleDaemons) {
for (DaemonInfo daemon : compatibleDaemons) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
} catch (DaemonException.ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Trying a different daemon...", daemon, e);
}
}
return null;
}
public DaemonClientConnection startDaemon(DaemonCompatibilitySpec constraint) {
final String daemon = daemonStarter.startDaemon();
LOGGER.debug("Started Maven daemon {}", daemon);
long start = System.currentTimeMillis();
do {
DaemonClientConnection daemonConnection = connectToDaemonWithId(daemon);
if (daemonConnection != null) {
return daemonConnection;
}
try {
sleep(200L);
} catch (InterruptedException e) {
throw new DaemonException.InterruptedException(e);
}
} while (System.currentTimeMillis() - start < DEFAULT_CONNECT_TIMEOUT);
DaemonDiagnostics diag = new DaemonDiagnostics(daemon, layout.daemonLog(daemon));
throw new DaemonException.ConnectException("Timeout waiting to connect to the Maven daemon.\n" + diag.describe());
}
private DaemonClientConnection connectToDaemonWithId(String daemon) throws DaemonException.ConnectException {
// Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that nobody else will grab it.
DaemonInfo daemonInfo = registry.get(daemon);
if (daemonInfo != null) {
try {
return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
} catch (DaemonException.ConnectException e) {
DaemonDiagnostics diag = new DaemonDiagnostics(daemon, layout.daemonLog(daemon));
throw new DaemonException.ConnectException("Could not connect to the Maven daemon.\n" + diag.describe(), e);
}
}
return null;
}
private DaemonClientConnection connectToDaemon(DaemonInfo daemon, DaemonClientConnection.StaleAddressDetector staleAddressDetector) throws DaemonException.ConnectException {
LOGGER.debug("Connecting to Daemon");
try {
DaemonConnection<Message> connection = connect(daemon.getAddress());
return new DaemonClientConnection(connection, daemon, staleAddressDetector);
} catch (DaemonException.ConnectException e) {
staleAddressDetector.maybeStaleAddress(e);
throw e;
} finally {
LOGGER.debug("Connected");
}
}
private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
private final DaemonInfo daemon;
private final boolean exposeAsStale;
public CleanupOnStaleAddress(DaemonInfo daemon, boolean exposeAsStale) {
this.daemon = daemon;
this.exposeAsStale = exposeAsStale;
}
@Override
public boolean maybeStaleAddress(Exception failure) {
LOGGER.info("Removing daemon from the registry due to communication failure. Daemon information: {}", daemon);
final long timestamp = System.currentTimeMillis();
final DaemonStopEvent stopEvent = new DaemonStopEvent(daemon.getUid(), timestamp, null, "by user or operating system");
registry.storeStopEvent(stopEvent);
registry.remove(daemon.getUid());
return exposeAsStale;
}
}
public DaemonConnection<Message> connect(int port) throws DaemonException.ConnectException {
InetSocketAddress address = new InetSocketAddress(port);
try {
LOGGER.debug("Trying to connect to address {}.", address);
SocketChannel socketChannel = SocketChannel.open();
Socket socket = socketChannel.socket();
socket.connect(address, CONNECT_TIMEOUT);
if (socket.getLocalSocketAddress().equals(socket.getRemoteSocketAddress())) {
socketChannel.close();
throw new DaemonException.ConnectException(String.format("Socket connected to itself on %s.", address));
}
LOGGER.debug("Connected to address {}.", socket.getRemoteSocketAddress());
return new DaemonConnection<>(socketChannel, serializer);
} catch (DaemonException.ConnectException e) {
throw e;
} catch (Exception e) {
throw new DaemonException.ConnectException(String.format("Could not connect to server %s.", address), e);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.stream.Collector;
public class DaemonDiagnostics {
private final static int TAIL_SIZE = 20;
private final String uid;
private final Path daemonLog;
public DaemonDiagnostics(String uid, Path daemonLog) {
this.uid = uid;
this.daemonLog = daemonLog;
}
@Override
public String toString() {
return "{"
+ "uid=" + uid
+ ", daemonLog=" + daemonLog
+ '}';
}
private String tailDaemonLog() {
try {
String tail = tail(daemonLog, TAIL_SIZE);
return formatTail(tail);
} catch (IOException e) {
return "Unable to read from the daemon log file: " + daemonLog + ", because of: " + e.getCause();
}
}
/**
* @param path to read from tail
* @param maxLines max lines to read
* @return tail content
* @throws IOException when reading failed
*/
static String tail(Path path, int maxLines) throws IOException {
try (BufferedReader r = Files.newBufferedReader(path)) {
return String.join("\n", r.lines().collect(lastN(maxLines)));
}
}
static <T> Collector<T, ?, List<T>> lastN(int n) {
return Collector.<T, Deque<T>, List<T>>of(ArrayDeque::new, (acc, t) -> {
if (acc.size() == n)
acc.pollFirst();
acc.add(t);
}, (acc1, acc2) -> {
while (acc2.size() < n && !acc1.isEmpty()) {
acc2.addFirst(acc1.pollLast());
}
return acc2;
}, ArrayList::new);
}
private String formatTail(String tail) {
return "----- Last " + TAIL_SIZE + " lines from daemon log file - " + daemonLog + " -----\n"
+ tail
+ "----- End of the daemon log -----\n";
}
public String describe() {
return "Daemon uid: " + uid + "\n"
+ " log file: " + daemonLog + "\n"
+ tailDaemonLog();
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
public class DaemonException extends RuntimeException {
public DaemonException(String message) {
super(message);
}
public DaemonException(String message, Throwable cause) {
super(message, cause);
}
public DaemonException(Throwable cause) {
super(cause);
}
public static class InterruptedException extends DaemonException {
public InterruptedException(Throwable cause) {
super(cause);
}
}
public static class ConnectException extends DaemonException {
public ConnectException(String message) {
super(message);
}
public ConnectException(String message, Throwable cause) {
super(message, cause);
}
}
public static class StartException extends DaemonException {
public StartException(String message) {
super(message);
}
public StartException(String message, Throwable cause) {
super(message, cause);
}
}
public static class MessageIOException extends DaemonException {
public MessageIOException(String message) {
super(message);
}
public MessageIOException(String message, Throwable cause) {
super(message, cause);
}
}
public static class RecoverableMessageIOException extends MessageIOException {
public RecoverableMessageIOException(String message) {
super(message);
}
public RecoverableMessageIOException(String message, Throwable cause) {
super(message, cause);
}
}
public static class StaleAddressException extends DaemonException {
public StaleAddressException(String message) {
super(message);
}
public StaleAddressException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@@ -0,0 +1,14 @@
package org.jboss.fuse.mvnd.client;
/**
* Expiration status for daemon expiration check results.
* Note that order here is important, higher ordinal statuses
* take precedent over lower ordinal statuses when aggregating
* results.
*/
public enum DaemonExpirationStatus {
DO_NOT_EXPIRE,
QUIET_EXPIRE,
GRACEFUL_EXPIRE,
IMMEDIATE_EXPIRE;
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import static org.jboss.fuse.mvnd.client.DaemonState.Busy;
import static org.jboss.fuse.mvnd.client.DaemonState.Idle;
import java.util.List;
public class DaemonInfo {
private final String uid;
private final String javaHome;
private final String mavenHome;
private final int pid;
private final int address;
private final int idleTimeout;
private final String locale;
private final List<String> options;
private final DaemonState state;
private final long lastIdle;
private final long lastBusy;
public DaemonInfo(String uid, String javaHome, String mavenHome,
int pid, int address, int idleTimeout,
String locale, List<String> options,
DaemonState state, long lastIdle, long lastBusy) {
this.uid = uid;
this.javaHome = javaHome;
this.mavenHome = mavenHome;
this.pid = pid;
this.address = address;
this.idleTimeout = idleTimeout;
this.locale = locale;
this.options = options;
this.state = state;
this.lastIdle = lastIdle;
this.lastBusy = lastBusy;
}
public String getUid() {
return uid;
}
public String getJavaHome() {
return javaHome;
}
public String getMavenHome() {
return mavenHome;
}
public int getPid() {
return pid;
}
public int getAddress() {
return address;
}
public int getIdleTimeout() {
return idleTimeout;
}
public String getLocale() {
return locale;
}
public List<String> getOptions() {
return options;
}
public DaemonState getState() {
return state;
}
public long getLastIdle() {
return lastIdle;
}
public long getLastBusy() {
return lastBusy;
}
public DaemonInfo withState(DaemonState state) {
long lb, li;
if (this.state == Idle && state == Busy) {
li = lastIdle;
lb = System.currentTimeMillis();
} else if (this.state == Busy && state == Idle) {
li = System.currentTimeMillis();
lb = lastBusy;
} else {
li = lastIdle;
lb = lastBusy;
}
return new DaemonInfo(uid, javaHome, mavenHome, pid, address,
idleTimeout, locale, options, state, li, lb);
}
@Override
public String toString() {
return "DaemonInfo{" +
"uid='" + uid + '\'' +
", javaHome='" + javaHome + '\'' +
", mavenHome='" + mavenHome + '\'' +
", pid=" + pid +
", address=" + address +
", idleTimeout=" + idleTimeout +
", locale='" + locale + '\'' +
", options=" + options +
", state=" + state +
", lastIdle=" + lastIdle +
", lastBusy=" + lastBusy +
'}';
}
}

View File

@@ -0,0 +1,535 @@
/*
* Copyright 2011 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import static org.jboss.fuse.mvnd.client.DaemonState.Canceled;
import static org.jboss.fuse.mvnd.client.DaemonState.Idle;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;
import sun.nio.ch.DirectBuffer;
/**
* Access to daemon registry files. Useful also for testing.
*/
public class DaemonRegistry implements AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(DaemonRegistry.class);
private static final int MAX_LENGTH = 32768;
private final Path registryFile;
private final Lock lock = new ReentrantLock();
private final FileChannel channel;
private final MappedByteBuffer buffer;
private long seq;
private final Map<String, DaemonInfo> infosMap = new HashMap<>();
private final List<DaemonStopEvent> stopEvents = new ArrayList<>();
public DaemonRegistry(Path registryFile) {
this.registryFile = registryFile;
try {
if (!Files.isRegularFile(registryFile)) {
if (!Files.isDirectory(registryFile.getParent())) {
Files.createDirectories(registryFile.getParent());
}
}
channel = FileChannel.open(registryFile,
StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, MAX_LENGTH);
} catch (IOException e) {
throw new DaemonException(e);
}
}
public void close() {
try {
channel.close();
} catch (IOException e) {
throw new DaemonException("Error closing registry", e);
}
}
public Path getRegistryFile() {
return registryFile;
}
public DaemonInfo get(String uid) {
lock.lock();
try {
read();
return infosMap.get(uid);
} finally {
lock.unlock();
}
}
public List<DaemonInfo> getAll() {
lock.lock();
try {
read();
return new ArrayList<>(infosMap.values());
} finally {
lock.unlock();
}
}
public List<DaemonInfo> getIdle() {
lock.lock();
try {
read();
return infosMap.values().stream()
.filter(di -> di.getState() == Idle)
.collect(Collectors.toList());
} finally {
lock.unlock();
}
}
public List<DaemonInfo> getNotIdle() {
lock.lock();
try {
read();
return infosMap.values().stream()
.filter(di -> di.getState() != Idle)
.collect(Collectors.toList());
} finally {
lock.unlock();
}
}
public List<DaemonInfo> getCanceled() {
lock.lock();
try {
read();
return infosMap.values().stream()
.filter(di -> di.getState() == Canceled)
.collect(Collectors.toList());
} finally {
lock.unlock();
}
}
public void remove(final String uid) {
lock.lock();
LOGGER.debug("Removing daemon uid: {}", uid);
try {
update(() -> infosMap.remove(uid));
} finally {
lock.unlock();
}
}
public void markState(final String uid, final DaemonState state) {
lock.lock();
LOGGER.debug("Marking busy by uid: {}", uid);
try {
update(() -> infosMap.computeIfPresent(uid, (id, di) -> di.withState(state)));
} finally {
lock.unlock();
}
}
public void storeStopEvent(final DaemonStopEvent stopEvent) {
lock.lock();
LOGGER.debug("Storing daemon stop event with timestamp {}", stopEvent.getTimestamp());
try {
update(() -> stopEvents.add(stopEvent));
} finally {
lock.unlock();
}
}
public List<DaemonStopEvent> getStopEvents() {
lock.lock();
LOGGER.debug("Getting daemon stop events");
try {
read();
return new ArrayList<>(stopEvents);
} finally {
lock.unlock();
}
}
public void removeStopEvents(final Collection<DaemonStopEvent> events) {
lock.lock();
LOGGER.info("Removing {} daemon stop events from registry", events.size());
try {
update(() -> stopEvents.removeAll(events));
} finally {
lock.unlock();
}
}
public void store(final DaemonInfo info) {
lock.lock();
LOGGER.debug("Storing daemon {}", info);
try {
update(() -> infosMap.put(info.getUid(), info));
} finally {
lock.unlock();
}
}
private static final long OFFSET_LOCK = 0;
private static final long OFFSET_SEQ = OFFSET_LOCK + Long.BYTES;
private static final long OFFSET_DATA = OFFSET_SEQ + Long.BYTES;
private void read() {
doUpdate(null);
}
private void update(Runnable updater) {
doUpdate(updater);
}
private void doUpdate(Runnable updater) {
if (!Files.isReadable(getRegistryFile())) {
throw new DaemonException("Registry became unaccessible");
}
try {
busyLockLong(OFFSET_LOCK);
try {
long newSeq = readLong(OFFSET_SEQ);
if (newSeq != seq) {
seq = newSeq;
BufferCaster.cast(buffer).position((int) OFFSET_DATA);
infosMap.clear();
int nb = buffer.getInt();
for (int i = 0; i < nb; i++) {
String uid = readString();
String javaHome = readString();
String mavenHome = readString();
int pid = buffer.getInt();
int address = buffer.getInt();
int idle = buffer.getInt();
String locale = readString();
List<String> opts = new ArrayList<>();
int nbOpts = buffer.getInt();
for (int j = 0; j < nbOpts; j++) {
opts.add(readString());
}
DaemonState state = DaemonState.values()[buffer.get()];
long lastIdle = buffer.getLong();
long lastBusy = buffer.getLong();
DaemonInfo di = new DaemonInfo(uid, javaHome, mavenHome, pid, address, idle, locale, opts, state, lastIdle, lastBusy);
infosMap.putIfAbsent(di.getUid(), di);
}
stopEvents.clear();
nb = buffer.getInt();
for (int i = 0; i < nb; i++) {
String uid = readString();
long date = buffer.getLong();
int ord = buffer.get();
DaemonExpirationStatus des = ord >= 0 ? DaemonExpirationStatus.values()[ord] : null;
String reason = readString();
DaemonStopEvent se = new DaemonStopEvent(uid, date, des, reason);
stopEvents.add(se);
}
}
if (updater != null) {
updater.run();
writeLong(OFFSET_SEQ, ++seq);
BufferCaster.cast(buffer).position((int) OFFSET_DATA);
buffer.putInt(infosMap.size());
for (DaemonInfo di : infosMap.values()) {
writeString(di.getUid());
writeString(di.getJavaHome());
writeString(di.getMavenHome());
buffer.putInt(di.getPid());
buffer.putInt(di.getAddress());
buffer.putInt(di.getIdleTimeout());
writeString(di.getLocale());
buffer.putInt(di.getOptions().size());
for (String opt : di.getOptions()) {
writeString(opt);
}
buffer.put((byte) di.getState().ordinal());
buffer.putLong(di.getLastIdle());
buffer.putLong(di.getLastBusy());
}
buffer.putInt(stopEvents.size());
for (DaemonStopEvent dse : stopEvents) {
writeString(dse.getUid());
buffer.putLong(dse.getTimestamp());
buffer.put((byte) (dse.getStatus() == null ? -1 : dse.getStatus().ordinal()));
writeString(dse.getReason());
}
}
} finally {
unlockLong(OFFSET_LOCK);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private static final int PROCESS_ID = getProcessId0();
private static int getProcessId0() {
String pid = null;
final File self = new File("/proc/self");
try {
if (self.exists()) {
pid = self.getCanonicalFile().getName();
}
} catch (IOException ignored) {
}
if (pid == null) {
pid = ManagementFactory.getRuntimeMXBean().getName().split("@", 0)[0];
}
if (pid == null) {
int rpid = new Random().nextInt(1 << 16);
LOGGER.warn("Unable to determine PID, picked a random number=" + rpid);
return rpid;
} else {
return Integer.parseInt(pid);
}
}
private static long uniqueTid() {
// Assume 48 bit for 16 to 24-bit process id and 16 million threads from the start.
return ((long) getProcessId() << 24) | currentThread().getId();
}
public static int getProcessId() {
return PROCESS_ID;
}
private static Thread currentThread() {
return Thread.currentThread();
}
static final int SLEEP_THRESHOLD = 20 * 1000 * 1000;
static final long BUSY_LOCK_LIMIT = 20L * 1000 * 1000 * 1000;
public void busyLockLong(long offset) throws InterruptedException, IllegalStateException {
boolean success = tryLockNanosLong(offset, BUSY_LOCK_LIMIT);
if (!success)
if (currentThread().isInterrupted())
throw new InterruptedException();
else
throw new IllegalStateException("Failed to acquire lock after " + BUSY_LOCK_LIMIT / 1e9 + " seconds.");
}
public void unlockLong(long offset) throws IllegalMonitorStateException {
long id = uniqueTid();
long firstValue = (1L << 48) | id;
if (compareAndSwapLong(offset, firstValue, 0))
return;
// try to check the lowId and the count.
unlockFailedLong(offset, id);
}
public void resetLockLong(long offset) {
writeOrderedLong(offset, 0L);
}
public boolean tryLockLong(long offset) {
long id = uniqueTid();
return tryLockNanos8a(offset, id);
}
public boolean tryLockNanosLong(long offset, long nanos) {
long id = uniqueTid();
int limit = nanos <= 10000 ? (int) nanos / 10 : 1000;
for (int i = 0; i < limit; i++)
if (tryLockNanos8a(offset, id))
return true;
if (nanos <= 10000)
return false;
return tryLockNanosLong0(offset, nanos, id);
}
private boolean tryLockNanosLong0(long offset, long nanos, long id) {
long nanos0 = Math.min(nanos, SLEEP_THRESHOLD);
long start = System.nanoTime();
long end0 = start + nanos0 - 10000;
do {
if (tryLockNanos8a(offset, id))
return true;
} while (end0 > System.nanoTime() && !currentThread().isInterrupted());
long end = start + nanos - SLEEP_THRESHOLD;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(currentThread().getName() + ", waiting for lock");
}
try {
do {
if (tryLockNanos8a(offset, id)) {
long millis = (System.nanoTime() - start) / 1000000;
if (millis > 200) {
LOGGER.warn(currentThread().getName() +
", to obtain a lock took " +
millis / 1e3 + " seconds"
);
}
return true;
}
Thread.sleep(1);
} while (end > System.nanoTime());
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
return false;
}
private boolean tryLockNanos8a(long offset, long id) {
long firstValue = (1L << 48) | id;
if (compareAndSwapLong(offset, 0, firstValue))
return true;
long currentValue = readLong(offset);
long lockedId = currentValue & ((1L << 48) - 1);
if (lockedId == 0) {
int count = (int) (currentValue >>> 48);
if (count != 0)
LOGGER.warn("Lock held by threadId 0 !?");
return compareAndSwapLong(offset, currentValue, firstValue);
}
if (lockedId == id) {
if (currentValue >>> 48 == 65535)
throw new IllegalStateException("Reentered 65535 times without an unlock");
currentValue += 1L << 48;
writeOrderedLong(offset, currentValue);
return true;
}
return false;
}
private void unlockFailedLong(long offset, long id) throws IllegalMonitorStateException {
long currentValue = readLong(offset);
long holderId = currentValue & (-1L >>> 16);
if (holderId == id) {
currentValue -= 1L << 48;
writeOrderedLong(offset, currentValue);
} else if (currentValue == 0) {
throw new IllegalMonitorStateException("No thread holds this lock");
} else {
throw new IllegalMonitorStateException("Process " + ((currentValue >>> 32) & 0xFFFF)
+ " thread " + (holderId & (-1L >>> 32))
+ " holds this lock, " + (currentValue >>> 48)
+ " times, unlock from " + getProcessId()
+ " thread " + currentThread().getId());
}
}
static final Unsafe UNSAFE;
static final int BYTES_OFFSET;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
BYTES_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public boolean compareAndSwapLong(long offset, long expected, long x) {
if (buffer instanceof DirectBuffer)
return UNSAFE.compareAndSwapLong(null, ((DirectBuffer) buffer).address() + offset, expected, x);
return UNSAFE.compareAndSwapLong(buffer.array(), BYTES_OFFSET + offset, expected, x);
}
public long readVolatileLong(int offset) {
readBarrier();
return readLong(offset);
}
public long readLong(long offset) {
return buffer.getLong((int) offset);
}
public void writeOrderedLong(long offset, long v) {
writeLong(offset, v);
writeBarrier();
}
public void writeLong(long offset, long v) {
buffer.putLong((int) offset, v);
}
private AtomicBoolean barrier;
private void readBarrier() {
if (barrier == null) barrier = new AtomicBoolean();
barrier.get();
}
private void writeBarrier() {
if (barrier == null) barrier = new AtomicBoolean();
barrier.lazySet(false);
}
private String readString() {
int sz = buffer.getShort();
if (sz == -1) {
return null;
}
if (sz < -1 || sz > 1024) {
throw new IllegalStateException("Bad string size: " + sz);
}
byte[] buf = new byte[sz];
buffer.get(buf);
return new String(buf, StandardCharsets.UTF_8);
}
private void writeString(String str) {
if (str == null) {
buffer.putShort((short) -1);
} else if (str.length() > 1024) {
throw new IllegalStateException("String too long: " + str);
} else {
byte[] buf = str.getBytes(StandardCharsets.UTF_8);
buffer.putShort((short) buf.length);
buffer.put(buf);
}
}
public String toString() {
return String.format("PersistentDaemonRegistry[file=%s]", registryFile);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
public interface DaemonStarter {
String startDaemon();
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
public enum DaemonState {
Idle,
Busy,
Canceled,
StopRequested,
Stopped,
Broken
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright 2016 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.Serializable;
import java.text.DateFormat;
import java.util.Date;
import java.util.Objects;
/**
* Information regarding when and why a daemon was stopped.
*/
public class DaemonStopEvent implements Serializable {
private final String uid;
private final long timestamp;
private final DaemonExpirationStatus status;
private final String reason;
public DaemonStopEvent(String uid, long timestamp, DaemonExpirationStatus status, String reason) {
this.uid = uid;
this.timestamp = timestamp;
this.status = status;
this.reason = reason != null ? reason : "";
}
public String getUid() {
return uid;
}
public long getTimestamp() {
return timestamp;
}
public DaemonExpirationStatus getStatus() {
return status;
}
public String getReason() {
return reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DaemonStopEvent that = (DaemonStopEvent) o;
return Objects.equals(uid, that.uid)
&& timestamp == that.timestamp
&& status == that.status
&& Objects.equals(reason, that.reason);
}
@Override
public int hashCode() {
return Objects.hash(timestamp, uid, status, reason);
}
@Override
public String toString() {
return "DaemonStopEvent{"
+ "uid=" + uid
+ ", timestamp=" + DateFormat.getDateTimeInstance().format(new Date(timestamp))
+ ", status=" + status
+ ", reason=" + reason
+ "}";
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
public class Layout {
private static Layout ENV_INSTANCE;
private final Path javaHome;
private final Path mavenHome;
private final Path userDir;
private final Path multiModuleProjectDirectory;
public Layout(Path javaHome, Path mavenHome, Path userDir, Path multiModuleProjectDirectory) {
super();
this.javaHome = javaHome;
this.mavenHome = mavenHome;
this.userDir = userDir;
this.multiModuleProjectDirectory = multiModuleProjectDirectory;
}
public Path javaHome() {
return javaHome;
}
public Path mavenHome() {
return mavenHome;
}
public Path userDir() {
return userDir;
}
public Path registry() {
return mavenHome.resolve("daemon/registry.bin");
}
public Path daemonLog(String daemon) {
return mavenHome.resolve("daemon/daemon-" + daemon + ".log");
}
public Path multiModuleProjectDirectory() {
return multiModuleProjectDirectory;
}
private static String getProperty(String key) {
return Objects.requireNonNull(System.getProperty(key), "Undefined system property: " + key);
}
public static Layout getEnvInstance() {
if (ENV_INSTANCE == null) {
ENV_INSTANCE = new Layout(Paths.get(getProperty("java.home")).toAbsolutePath().normalize(),
Paths.get(getProperty("maven.home")).toAbsolutePath().normalize(),
Paths.get(getProperty("user.dir")).toAbsolutePath().normalize(),
Paths.get(getProperty("maven.multiModuleProjectDirectory")).toAbsolutePath().normalize());
}
return ENV_INSTANCE;
}
}

View File

@@ -0,0 +1,370 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UTFDataFormatException;
import java.util.ArrayList;
import java.util.List;
public abstract class Message {
final long timestamp = System.nanoTime();
public long timestamp() {
return timestamp;
}
public static class BuildRequest extends Message {
final List<String> args;
final String workingDir;
final String projectDir;
public BuildRequest(List<String> args, String workingDir, String projectDir) {
this.args = args;
this.workingDir = workingDir;
this.projectDir = projectDir;
}
public List<String> getArgs() {
return args;
}
public String getWorkingDir() {
return workingDir;
}
public String getProjectDir() {
return projectDir;
}
@Override
public String toString() {
return "BuildRequest{" +
"args=" + args +
", workingDir='" + workingDir + '\'' +
", projectDir='" + projectDir + '\'' +
'}';
}
}
public static class BuildException extends Message {
final String message;
final String className;
final String stackTrace;
public BuildException(Throwable t) {
this(t.getMessage(), t.getClass().getName(), getStackTrace(t));
}
static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw, true));
return sw.toString();
}
public BuildException(String message, String className, String stackTrace) {
this.message = message;
this.className = className;
this.stackTrace = stackTrace;
}
public String getMessage() {
return message;
}
public String getClassName() {
return className;
}
public String getStackTrace() {
return stackTrace;
}
@Override
public String toString() {
return "BuildException{" +
"message='" + message + '\'' +
", className='" + className + '\'' +
", stackTrace='" + stackTrace + '\'' +
'}';
}
}
public static class BuildEvent extends Message {
public enum Type {
BuildStarted, BuildStopped, ProjectStarted, ProjectStopped, MojoStarted, MojoStopped
}
final Type type;
final String projectId;
final String display;
public BuildEvent(Type type, String projectId, String display) {
this.type = type;
this.projectId = projectId;
this.display = display;
}
public Type getType() {
return type;
}
public String getProjectId() {
return projectId;
}
public String getDisplay() {
return display;
}
@Override
public String toString() {
return "BuildEvent{" +
"type=" + type +
", display='" + display + '\'' +
'}';
}
}
public static class BuildMessage extends Message {
final String message;
public BuildMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return "BuildMessage{" +
"message='" + message + '\'' +
'}';
}
}
public static class MessageSerializer implements Serializer<Message> {
final int BUILD_REQUEST = 0;
final int BUILD_EVENT = 1;
final int BUILD_MESSAGE = 2;
final int BUILD_EXCEPTION = 3;
@Override
public Message read(DataInputStream input) throws EOFException, Exception {
int type = input.read();
if (type == -1) {
return null;
}
switch (type) {
case BUILD_REQUEST:
return readBuildRequest(input);
case BUILD_EVENT:
return readBuildEvent(input);
case BUILD_MESSAGE:
return readBuildMessage(input);
case BUILD_EXCEPTION:
return readBuildException(input);
}
throw new IllegalStateException("Unexpected message type: " + type);
}
@Override
public void write(DataOutputStream output, Message value) throws Exception {
if (value instanceof BuildRequest) {
output.write(BUILD_REQUEST);
writeBuildRequest(output, (BuildRequest) value);
} else if (value instanceof BuildEvent) {
output.write(BUILD_EVENT);
writeBuildEvent(output, (BuildEvent) value);
} else if (value instanceof BuildMessage) {
output.write(BUILD_MESSAGE);
writeBuildMessage(output, (BuildMessage) value);
} else if (value instanceof BuildException) {
output.write(BUILD_EXCEPTION);
writeBuildException(output, (BuildException) value);
} else {
throw new IllegalStateException();
}
}
private BuildRequest readBuildRequest(DataInputStream input) throws IOException {
List<String> args = readStringList(input);
String workingDir = readUTF(input);
String projectDir = readUTF(input);
return new BuildRequest(args, workingDir, projectDir);
}
private void writeBuildRequest(DataOutputStream output, BuildRequest value) throws IOException {
writeStringList(output, value.args);
writeUTF(output, value.workingDir);
writeUTF(output, value.projectDir);
}
private BuildEvent readBuildEvent(DataInputStream input) throws IOException {
BuildEvent.Type type = BuildEvent.Type.values()[input.read()];
String projectId = readUTF(input);
String display = readUTF(input);
return new BuildEvent(type, projectId, display);
}
private void writeBuildEvent(DataOutputStream output, BuildEvent value) throws IOException {
output.write(value.type.ordinal());
writeUTF(output, value.projectId);
writeUTF(output, value.display);
}
private BuildMessage readBuildMessage(DataInputStream input) throws IOException {
String message = readUTF(input);
return new BuildMessage(message);
}
private void writeBuildMessage(DataOutputStream output, BuildMessage value) throws IOException {
writeUTF(output, value.message);
}
private BuildException readBuildException(DataInputStream input) throws IOException {
String message = readUTF(input);
String className = readUTF(input);
String stackTrace = readUTF(input);
return new BuildException(message, className, stackTrace);
}
private void writeBuildException(DataOutputStream output, BuildException value) throws IOException {
writeUTF(output, value.message);
writeUTF(output, value.className);
writeUTF(output, value.stackTrace);
}
private List<String> readStringList(DataInputStream input) throws IOException {
ArrayList<String> l = new ArrayList<>();
int nb = input.readInt();
for (int i = 0; i < nb; i++) {
l.add(readUTF(input));
}
return l;
}
private void writeStringList(DataOutputStream output, List<String> value) throws IOException {
output.writeInt(value.size());
for (String v : value) {
writeUTF(output, v);
}
}
private static final String INVALID_BYTE = "Invalid byte";
private static final int UTF_BUFS_CHAR_CNT = 256;
private static final int UTF_BUFS_BYTE_CNT = UTF_BUFS_CHAR_CNT * 3;
final byte[] byteBuf = new byte[UTF_BUFS_BYTE_CNT];
String readUTF(DataInputStream input) throws IOException {
int len = input.readInt();
final char[] chars = new char[len];
int i = 0, cnt = 0, charIdx = 0;
while (charIdx < len) {
if (i == cnt) {
cnt = input.read(byteBuf, 0, Math.min(UTF_BUFS_BYTE_CNT, len - charIdx));
if (cnt < 0) {
throw new EOFException();
}
i = 0;
}
final int a = byteBuf[i++] & 0xff;
if (a < 0x80) {
// low bit clear
chars[charIdx++] = (char) a;
} else if (a < 0xc0) {
throw new UTFDataFormatException(INVALID_BYTE);
} else if (a < 0xe0) {
if (i == cnt) {
cnt = input.read(byteBuf, 0, Math.min(UTF_BUFS_BYTE_CNT, len - charIdx));
if (cnt < 0) {
throw new EOFException();
}
i = 0;
}
final int b = byteBuf[i++] & 0xff;
if ((b & 0xc0) != 0x80) {
throw new UTFDataFormatException(INVALID_BYTE);
}
chars[charIdx++] = (char) ((a & 0x1f) << 6 | b & 0x3f);
} else if (a < 0xf0) {
if (i == cnt) {
cnt = input.read(byteBuf, 0, Math.min(UTF_BUFS_BYTE_CNT, len - charIdx));
if (cnt < 0) {
throw new EOFException();
}
i = 0;
}
final int b = byteBuf[i++] & 0xff;
if ((b & 0xc0) != 0x80) {
throw new UTFDataFormatException(INVALID_BYTE);
}
if (i == cnt) {
cnt = input.read(byteBuf, 0, Math.min(UTF_BUFS_BYTE_CNT, len - charIdx));
if (cnt < 0) {
throw new EOFException();
}
i = 0;
}
final int c = byteBuf[i++] & 0xff;
if ((c & 0xc0) != 0x80) {
throw new UTFDataFormatException(INVALID_BYTE);
}
chars[charIdx++] = (char) ((a & 0x0f) << 12 | (b & 0x3f) << 6 | c & 0x3f);
} else {
throw new UTFDataFormatException(INVALID_BYTE);
}
}
return String.valueOf(chars);
}
void writeUTF(DataOutputStream output, String s) throws IOException {
final int length = s.length();
output.writeInt(length);
int strIdx = 0;
int byteIdx = 0;
while (strIdx < length) {
final char c = s.charAt(strIdx++);
if (c > 0 && c <= 0x7f) {
byteBuf[byteIdx++] = (byte) c;
} else if (c <= 0x07ff) {
byteBuf[byteIdx++] = (byte) (0xc0 | 0x1f & c >> 6);
byteBuf[byteIdx++] = (byte) (0x80 | 0x3f & c);
} else {
byteBuf[byteIdx++] = (byte) (0xe0 | 0x0f & c >> 12);
byteBuf[byteIdx++] = (byte) (0x80 | 0x3f & c >> 6);
byteBuf[byteIdx++] = (byte) (0x80 | 0x3f & c);
}
if (byteIdx > UTF_BUFS_BYTE_CNT - 4) {
output.write(byteBuf, 0, byteIdx);
byteIdx = 0;
}
}
if (byteIdx > 0) {
output.write(byteBuf, 0, byteIdx);
}
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2009 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
public interface Serializer<T> {
/**
* Reads the next object from the given stream. The implementation must not perform any buffering, so that it reads only those bytes from the input stream that are
* required to deserialize the next object.
*
* @throws EOFException When the next object cannot be fully read due to reaching the end of stream.
*/
T read(DataInputStream input) throws EOFException, Exception;
/**
* Writes the given object to the given stream. The implementation must not perform any buffering.
*/
void write(DataOutputStream output, T value) throws Exception;
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.client;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class ServerMain {
public static void main(String[] args) throws Exception {
String uidStr = System.getProperty("daemon.uid");
String mavenHomeStr = System.getProperty("maven.home");
if (uidStr == null || mavenHomeStr == null) {
throw new IllegalStateException("The system properties 'daemon.uid' and 'maven.home' must be valid");
}
Path mavenHome = Paths.get(mavenHomeStr);
URL[] classpath =
Stream.concat(
Stream.concat(Files.list(mavenHome.resolve("lib/ext")),
Files.list(mavenHome.resolve("lib")))
.filter(p -> p.getFileName().toString().endsWith(".jar"))
.filter(Files::isRegularFile),
Stream.of(mavenHome.resolve("conf"), mavenHome.resolve("conf/logging")))
.map(Path::normalize)
.map(Path::toUri)
.map(uri -> {
try {
return uri.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
})
.toArray(URL[]::new);
ClassLoader loader = new URLClassLoader(classpath, null) {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
return ServerMain.class.getClassLoader().loadClass(name);
}
}
};
Thread.currentThread().setContextClassLoader(loader);
Class<?> clazz = loader.loadClass("org.jboss.fuse.mvnd.daemon.Server");
try (AutoCloseable server = (AutoCloseable) clazz.getConstructor(String.class).newInstance(uidStr)) {
((Runnable) server).run();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.jboss.fuse.mvnd.jpm;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
/**
* Interface representing a process
*/
public interface Process extends Serializable {
/**
* Retrieves the PID of the process
* @return the pid
*/
int getPid();
/**
* Check if this process is still running
* @return <code>true</code> if the process is running
* @throws IOException if an error occurs
*/
boolean isRunning() throws IOException;
/**
* Destroy the process.
*
* @throws IOException If an error occurs.
*/
void destroy() throws IOException;
static Process create(File dir, String command) throws IOException {
return ProcessImpl.create(dir, command);
}
static Process attach(int pid) throws IOException {
return ProcessImpl.attach(pid);
}
}

View File

@@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.jboss.fuse.mvnd.jpm;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.util.HashMap;
import java.util.Map;
public class ProcessImpl implements Process {
/**
*
*/
private static final long serialVersionUID = -8140632422386086507L;
private int pid;
//private File input;
//private File output;
//private File error;
public ProcessImpl(int pid/*, File input, File output, File error*/) {
this.pid = pid;
//this.input = input;
//this.output = output;
//this.error = error;
}
public int getPid() {
return pid;
}
public boolean isRunning() throws IOException {
if (ScriptUtils.isWindows()) {
Map<String, String> props = new HashMap<>();
props.put("${pid}", Integer.toString(pid));
int ret = ScriptUtils.execute("running", props);
return ret == 0;
} else {
try {
java.lang.Process process = new java.lang.ProcessBuilder("ps", "-p", Integer.toString(pid)).start();
BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream()));
r.readLine(); // skip headers
String s = r.readLine();
boolean running = s != null && s.length() > 0;
process.waitFor();
return running;
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
public void destroy() throws IOException {
int ret;
if (ScriptUtils.isWindows()) {
Map<String, String> props = new HashMap<>();
props.put("${pid}", Integer.toString(pid));
ret = ScriptUtils.execute("destroy", props);
} else {
ret = ScriptUtils.executeProcess(new java.lang.ProcessBuilder("kill", "-9", Integer.toString(pid)));
}
if (ret != 0) {
throw new IOException("Unable to destroy process, it may already be terminated");
}
}
/*
public OutputStream getInputStream() throws FileNotFoundException {
return new FileOutputStream(input);
}
public InputStream getOutputStream() throws FileNotFoundException {
return new FileInputStream(output);
}
public InputStream getErrorStream() throws FileNotFoundException {
return new FileInputStream(error);
}
*/
public int waitFor() throws InterruptedException {
return 0;
}
public int exitValue() {
return 0;
}
public static Process create(File dir, String command) throws IOException {
//File input = File.createTempFile("jpm.", ".input");
//File output = File.createTempFile("jpm.", ".output");
//File error = File.createTempFile("jpm.", ".error");
File pidFile = File.createTempFile("jpm.", ".pid");
try {
Map<String, String> props = new HashMap<>();
//props.put("${in.file}", input.getCanonicalPath());
//props.put("${out.file}", output.getCanonicalPath());
//props.put("${err.file}", error.getCanonicalPath());
props.put("${pid.file}", pidFile.getCanonicalPath());
props.put("${dir}", dir != null ? dir.getCanonicalPath() : "");
if (ScriptUtils.isWindows()) {
command = command.replaceAll("\"", "\"\"");
}
props.put("${command}", command);
int ret = ScriptUtils.execute("start", props);
if (ret != 0) {
throw new IOException("Unable to create process (error code: " + ret + ")");
}
int pid = readPid(pidFile);
return new ProcessImpl(pid/*, input, output, error*/);
} finally {
pidFile.delete();
}
}
public static Process attach(int pid) throws IOException {
return new ProcessImpl(pid);
}
private static int readPid(File pidFile) throws IOException {
try (InputStream is = new FileInputStream(pidFile)) {
BufferedReader r = new BufferedReader(new InputStreamReader(is));
String pidString = r.readLine();
return Integer.parseInt(pidString);
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.jboss.fuse.mvnd.jpm;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.Scanner;
public class ScriptUtils {
public static int execute(String name, Map<String, String> props) throws IOException {
File script = File.createTempFile("jpm.", ".script");
try {
if (isWindows()) {
String res = "windows/" + name + ".vbs";
ScriptUtils.copyFilteredResource(res, script, props);
return executeProcess(new java.lang.ProcessBuilder("cscript",
"/NOLOGO",
"//E:vbs",
script.getCanonicalPath()));
} else {
String res = "unix/" + name + ".sh";
ScriptUtils.copyFilteredResource(res, script, props);
return executeProcess(new java.lang.ProcessBuilder("/bin/sh",
script.getCanonicalPath()));
}
} finally {
script.delete();
}
}
public static int executeProcess(java.lang.ProcessBuilder builder) throws IOException {
try {
java.lang.Process process = builder.start();
return process.waitFor();
} catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
public static void copyFilteredResource(String resource, File outFile, Map<String, String> props) throws IOException {
InputStream is = null;
try {
is = ScriptUtils.class.getResourceAsStream(resource);
// Read it line at a time so that we can use the platform line ending when we write it out.
PrintStream out = new PrintStream(new FileOutputStream(outFile));
try {
Scanner scanner = new Scanner(is);
while (scanner.hasNextLine() ) {
String line = scanner.nextLine();
line = filter(line, props);
out.println(line);
}
scanner.close();
} finally {
safeClose(out);
}
} finally {
safeClose(is);
}
}
private static void safeClose(InputStream is) throws IOException {
if (is == null) {
return;
}
try {
is.close();
} catch (Throwable ignore) {
}
}
private static void safeClose(OutputStream is) throws IOException {
if (is == null) {
return;
}
try {
is.close();
} catch (Throwable ignore) {
}
}
private static String filter(String line, Map<String, String> props) {
for (Map.Entry<String, String> i : props.entrySet()) {
int p1 = line.indexOf(i.getKey());
if( p1 >= 0 ) {
String l1 = line.substring(0, p1);
String l2 = line.substring(p1+i.getKey().length());
line = l1+i.getValue()+l2;
}
}
return line;
}
private static final boolean windows;
static {
windows = System.getProperty("os.name").toLowerCase().contains("windows");
}
public static boolean isWindows() {
return windows;
}
public static String getJavaCommandPath() throws IOException {
return new File(System.getProperty("java.home"), isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath();
}
}

View File

@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
buildNumber=${buildNumber}
timestamp=${timestamp}
version=${project.version}
distributionId=${distributionId}
distributionShortName=${distributionShortName}
distributionName=${distributionName}

View File

@@ -0,0 +1,29 @@
#!/bin/sh
################################################################################
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
################################################################################
#exec 1>${out.file}
#exec 2>${err.file}
exec 1>/dev/null
exec 2>/dev/null
if [ "x${dir}" != "x" ]; then
cd "${dir}"
fi
nohup ${command} &
echo $! > "${pid.file}"

View File

@@ -0,0 +1,27 @@
'===============================================================================
'
' Licensed to the Apache Software Foundation (ASF) under one or more
' contributor license agreements. See the NOTICE file distributed with
' this work for additional information regarding copyright ownership.
' The ASF licenses this file to You 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.
'
'===============================================================================
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = ${pid}")
intRetVal = 1
For Each objProcess in colProcessList
objProcess.Terminate()
intRetVal = 0
Next
WScript.Quit(intRetVal)

View File

@@ -0,0 +1,26 @@
'===============================================================================
'
' Licensed to the Apache Software Foundation (ASF) under one or more
' contributor license agreements. See the NOTICE file distributed with
' this work for additional information regarding copyright ownership.
' The ASF licenses this file to You 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.
'
'===============================================================================
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where ProcessId = ${pid}")
intRetVal = 1
For Each objProcess in colProcessList
intRetVal = 0
Next
WScript.Quit(intRetVal)

View File

@@ -0,0 +1,34 @@
'===============================================================================
'
' Licensed to the Apache Software Foundation (ASF) under one or more
' contributor license agreements. See the NOTICE file distributed with
' this work for additional information regarding copyright ownership.
' The ASF licenses this file to You 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.
'
'===============================================================================
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set objConfig = objWMIService.Get("Win32_ProcessStartup").SpawnInstance_
objConfig.ShowWindow = SW_HIDE
objConfig.CreateFlags = 8
If Len("${dir}") > 0 Then
intReturn = objWMIService.Get("Win32_Process").Create("${command}", "${dir}", objConfig, intProcessID)
Else
intReturn = objWMIService.Get("Win32_Process").Create("${command}", Null, objConfig, intProcessID)
End If
If intReturn = 0 Then
Set objOutputFile = CreateObject("Scripting.fileSystemObject").CreateTextFile("${pid.file}", TRUE)
objOutputFile.WriteLine(intProcessID)
objOutputFile.Close
End If
WScript.Quit(intReturn)

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2019 the original author or authors.
*
* 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.
*/
package org.jboss.fuse.mvnd.daemon;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
import org.jboss.fuse.mvnd.client.DaemonInfo;
import org.jboss.fuse.mvnd.client.DaemonRegistry;
import org.jboss.fuse.mvnd.client.DaemonState;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class DaemonRegistryTest {
@Test
public void testReadWrite() throws IOException {
Path temp = File.createTempFile("reg", ".data").toPath();
DaemonRegistry reg1 = new DaemonRegistry(temp);
DaemonRegistry reg2 = new DaemonRegistry(temp);
assertNotNull(reg1.getAll());
assertEquals(0, reg1.getAll().size());
assertNotNull(reg2.getAll());
assertEquals(0, reg2.getAll().size());
byte[] token = new byte[16];
new Random().nextBytes(token);
reg1.store(new DaemonInfo("the-uid", "/java/home/",
"/data/reg/", 0x12345678, 7502, 65536,
Locale.getDefault().toLanguageTag(), Arrays.asList("-Xmx"),
DaemonState.Idle, System.currentTimeMillis(), System.currentTimeMillis()));
assertNotNull(reg1.getAll());
assertEquals(1, reg1.getAll().size());
assertNotNull(reg2.getAll());
assertEquals(1, reg2.getAll().size());
}
}