/**
* 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.apache.cxf.testutil.common;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.StringUtils;
public class ServerLauncher {
public static final int DEFAULT_TIMEOUT = 3 * 60 * 1000;
protected static final String SERVER_FAILED =
"server startup failed (not a log message)";
private static final boolean DEFAULT_IN_PROCESS = false;
private static final Logger LOG = LogUtils.getLogger(ServerLauncher.class);
boolean serverPassed;
final String className;
private final boolean debug = false;
private boolean inProcess = DEFAULT_IN_PROCESS;
private AbstractTestServerBase inProcessServer;
private final String javaExe;
private Process process;
private boolean serverIsReady;
private boolean serverIsStopped;
private boolean serverLaunchFailed;
private Map<String, String> properties;
private String[] serverArgs;
private final Mutex mutex = new Mutex();
public ServerLauncher(String theClassName) {
this(theClassName, DEFAULT_IN_PROCESS);
}
public ServerLauncher(AbstractTestServerBase b) {
inProcess = true;
inProcessServer = b;
javaExe = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
className = null;
}
public ServerLauncher(String theClassName, boolean inprocess) {
inProcess = inprocess;
className = theClassName;
javaExe = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
}
public ServerLauncher(String theClassName, Map<String, String> p, String[] args) {
this(theClassName, p, args, false);
}
public ServerLauncher(String theClassName, Map<String, String> p, String[] args, boolean inprocess) {
className = theClassName;
properties = p;
serverArgs = args;
inProcess = inprocess;
javaExe = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
}
private boolean waitForServerToStop() {
synchronized (mutex) {
TimeoutCounter tc = new TimeoutCounter(DEFAULT_TIMEOUT);
while (!serverIsStopped) {
try {
mutex.wait(1000);
if (tc.isTimeoutExpired()) {
System.out.println("destroying server process");
process.destroy();
break;
}
} catch (InterruptedException ex) {
//ex.printStackTrace();
}
}
if (!inProcess) {
//wait for process to end...
tc = new TimeoutCounter(DEFAULT_TIMEOUT);
while (!tc.isTimeoutExpired()) {
try {
process.exitValue();
break;
} catch (IllegalThreadStateException ex) {
//ignore, process hasn't ended
try {
mutex.wait(1000);
} catch (InterruptedException ex1) {
//ignore
}
}
}
if (tc.isTimeoutExpired()) {
process.destroy();
}
}
}
return serverIsStopped;
}
public void signalStop() throws IOException {
if (process != null) {
process.getOutputStream().write('q');
process.getOutputStream().write('\n');
process.getOutputStream().flush();
}
}
public boolean stopServer() throws IOException {
if (inProcess) {
try {
return inProcessServer.stopInProcess();
} catch (Exception ex) {
ex.printStackTrace();
throw new IOException(ex.getMessage());
}
} else {
if (process != null) {
if (!serverIsStopped) {
try {
signalStop();
} catch (IOException ex) {
//ignore
}
}
waitForServerToStop();
process.destroy();
}
}
return serverPassed;
}
public boolean launchServer() throws IOException {
serverIsReady = false;
serverLaunchFailed = false;
if (inProcess) {
Class<?> cls;
Map<String, String> old = new HashMap<>();
try {
if (null != properties) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
old.put(entry.getKey(), System.getProperty(entry.getKey()));
if (entry.getValue() == null) {
System.clearProperty(entry.getKey());
} else {
System.setProperty(entry.getKey(), entry.getValue());
}
}
}
if (inProcessServer == null) {
cls = Class.forName(className);
Class<? extends AbstractTestServerBase> svcls =
cls.asSubclass(AbstractTestServerBase.class);
if (null == serverArgs) {
inProcessServer = svcls.newInstance();
} else {
Constructor<? extends AbstractTestServerBase> ctor
= svcls.getConstructor(serverArgs.getClass());
inProcessServer = ctor.newInstance(new Object[] {serverArgs});
}
}
inProcessServer.startInProcess();
serverIsReady = true;
} catch (Throwable ex) {
ex.printStackTrace();
serverLaunchFailed = true;
} finally {
for (Map.Entry<String, String> entry : old.entrySet()) {
if (entry.getValue() == null) {
System.clearProperty(entry.getKey());
} else {
System.setProperty(entry.getKey(), entry.getValue());
}
}
}
} else {
List<String> cmd;
try {
cmd = getCommand();
} catch (URISyntaxException e1) {
IOException ex = new IOException();
ex.initCause(e1);
throw ex;
}
LOG.fine("CMD: " + cmd);
if (debug) {
System.err.print("CMD: " + cmd);
}
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true);
process = pb.start();
OutputMonitorThread out = launchOutputMonitorThread(process.getInputStream(), System.out);
synchronized (mutex) {
TimeoutCounter tc = new TimeoutCounter(DEFAULT_TIMEOUT);
while (!(serverIsReady || serverLaunchFailed)) {
try {
mutex.wait(1000);
if (tc.isTimeoutExpired()) {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (serverLaunchFailed || !serverIsReady) {
System.err.println(out.getServerOutput());
}
}
return serverIsReady && !serverLaunchFailed;
}
public int waitForServer() {
int ret = -1;
try {
process.waitFor();
ret = process.exitValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
return ret;
}
private OutputMonitorThread launchOutputMonitorThread(final InputStream in, final PrintStream out) {
OutputMonitorThread t = new OutputMonitorThread(in, out);
t.start();
return t;
}
private class OutputMonitorThread extends Thread {
InputStream in;
PrintStream out;
StringBuilder serverOutputAll = new StringBuilder();
OutputMonitorThread(InputStream i, PrintStream o) {
in = i;
out = o;
}
public String getServerOutput() {
return serverOutputAll.toString();
}
public void run() {
String outputDir = System.getProperty("server.output.dir", "target/surefire-reports/");
OutputStream os = null;
try {
try {
os = Files.newOutputStream(Paths.get(outputDir + className + ".out"));
} catch (FileNotFoundException fex) {
outputDir = System.getProperty("basedir");
if (outputDir == null) {
outputDir = "target/surefire-reports/";
} else {
outputDir += "/target/surefire-reports/";
}
File file = new File(outputDir);
file.mkdirs();
os = Files.newOutputStream(Paths.get(outputDir + className + ".out"));
}
} catch (IOException ex) {
if (!ex.getMessage().contains("Stream closed")) {
ex.printStackTrace();
}
}
try (PrintStream ps = new PrintStream(os)) {
boolean running = true;
StringBuilder serverOutput = new StringBuilder();
for (int ch = in.read(); ch != -1; ch = in.read()) {
serverOutput.append((char)ch);
serverOutputAll.append((char)ch);
if (debug) {
System.err.print((char)ch);
}
String s = serverOutput.toString();
if (s.contains("server ready")) {
notifyServerIsReady();
} else if (s.contains("server passed")) {
serverPassed = true;
} else if (s.contains("server stopped")) {
notifyServerIsStopped();
running = false;
} else if (s.contains(SERVER_FAILED)) {
notifyServerFailed();
running = false;
}
if (ch == '\n' || !running) {
synchronized (out) {
ps.print(serverOutput.toString());
serverOutput.setLength(0);
ps.flush();
}
}
if (serverOutputAll.length() > 64000) {
serverOutputAll.delete(0, 10000);
}
}
} catch (IOException ex) {
if (!ex.getMessage().contains("Stream closed")) {
ex.printStackTrace();
}
}
}
}
void notifyServerIsReady() {
synchronized (mutex) {
serverIsReady = true;
mutex.notifyAll();
}
}
void notifyServerIsStopped() {
synchronized (mutex) {
LOG.info("notify server stopped");
serverIsStopped = true;
mutex.notifyAll();
}
}
void notifyServerFailed() {
synchronized (mutex) {
serverIsStopped = true;
serverLaunchFailed = true;
mutex.notifyAll();
}
}
private List<String> getCommand() throws URISyntaxException {
List<String> cmd = new ArrayList<>();
cmd.add(javaExe);
if (null != properties) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
cmd.add("-D" + entry.getKey() + "=" + entry.getValue());
}
}
for (Map.Entry<Object, Object> entry : TestUtil.getAllPorts().entrySet()) {
cmd.add("-D" + entry.getKey() + "=" + entry.getValue());
}
String vmargs = System.getProperty("server.launcher.vmargs");
if (StringUtils.isEmpty(vmargs)) {
cmd.add("-ea");
} else {
vmargs = vmargs.trim();
int idx = vmargs.indexOf(' ');
while (idx != -1) {
cmd.add(vmargs.substring(0, idx));
vmargs = vmargs.substring(idx + 1);
idx = vmargs.indexOf(' ');
}
cmd.add(vmargs);
}
String portClose = System.getProperty("org.apache.cxf.transports.http_jetty.DontClosePort");
if (portClose != null) {
cmd.add("-Dorg.apache.cxf.transports.http_jetty.DontClosePort=" + portClose);
}
String loggingPropertiesFile = System.getProperty("java.util.logging.config.file");
if (null != loggingPropertiesFile) {
cmd.add("-Djava.util.logging.config.file=" + loggingPropertiesFile);
}
cmd.add("-classpath");
ClassLoader loader = this.getClass().getClassLoader();
StringBuilder classpath = new StringBuilder(System.getProperty("java.class.path"));
if (classpath.indexOf("/.compatibility/") != -1) {
classpath.append(":");
//on OSX, the compatibility lib brclasspath.indexOf("/.compatibility/")
int idx = classpath.indexOf("/.compatibility/");
int idx1 = classpath.lastIndexOf(":", idx);
int idx2 = classpath.indexOf(":", idx);
classpath.replace(idx1, idx2, ":");
}
if (loader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader)loader).getURLs()) {
classpath.append(File.pathSeparatorChar);
classpath.append(url.toURI().getPath());
}
}
cmd.add(classpath.toString());
// If the client set the transformer factory property,
// we want the server to also set that property.
String transformerProperty = System.getProperty("javax.xml.transform.TransformerFactory");
if (null != transformerProperty) {
cmd.add("-Djavax.xml.transform.TransformerFactory=" + transformerProperty);
}
String validationMode = System.getProperty("spring.validation.mode");
if (null != validationMode) {
cmd.add("-Dspring.validation.mode=" + validationMode);
}
String derbyHome = System.getProperty("derby.system.home");
if (null != derbyHome) {
cmd.add("-Dderby.system.home=" + derbyHome);
}
String tmp = System.getProperty("java.io.tmpdir");
if (null != tmp) {
cmd.add("-Djava.io.tmpdir=" + tmp);
}
cmd.add(className);
if (null != serverArgs) {
for (String s : serverArgs) {
cmd.add(s);
}
}
return cmd;
}
static class Mutex {
// empty
}
static class TimeoutCounter {
private final long expectedEndTime;
TimeoutCounter(long theExpectedTimeout) {
expectedEndTime = System.currentTimeMillis() + theExpectedTimeout;
}
public boolean isTimeoutExpired() {
return System.currentTimeMillis() > expectedEndTime;
}
}
}