blob: 50c0fa9333fd838aa918870baeda9f754cd856b2 [file] [log] [blame]
/*
* 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.openejb.config;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.loader.Options;
import org.apache.openejb.util.Join;
import org.apache.openejb.util.Pipe;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
//import org.apache.openejb.loader.IO;
/**
* NOTE: Do not add inner or anonymous classes or a dependency without updating ExecMojo
*
* @version $Rev$ $Date$
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public class RemoteServer {
private static final Options options = new Options(System.getProperties());
public static final String SERVER_DEBUG_PORT = "server.debug.port";
public static final String SERVER_SHUTDOWN_PORT = "server.shutdown.port";
public static final String SERVER_SHUTDOWN_HOST = "server.shutdown.host";
public static final String SERVER_SHUTDOWN_COMMAND = "server.shutdown.command";
public static final String OPENEJB_SERVER_DEBUG = "openejb.server.debug";
public static final String START = "start";
public static final String STOP = "stop";
private final boolean debug = options.get(OPENEJB_SERVER_DEBUG, false);
private final boolean profile = options.get("openejb.server.profile", false);
private final boolean tomcat;
private final String javaOpts = System.getProperty("java.opts");
private String additionalClasspath;
/**
* Has the remote server's instance been already running ?
*/
private boolean serverHasAlreadyBeenStarted = true;
private Properties properties;
private final AtomicReference<Process> server = new AtomicReference<Process>();
private final int tries;
private final boolean verbose;
private final int portShutdown;
private final String host;
private final String command;
private File home;
private int portStartup;
public RemoteServer() {
this(options.get("connect.tries", 60), options.get("verbose", false));
}
public RemoteServer(final int tries, final boolean verbose) {
this.tries = (tries < 1 ? 1 : (tries > 3600 ? 3600 : tries)); //Wait at least 1 second to start or stop, but not more than an hour.
this.verbose = verbose;
home = getHome();
tomcat = (home != null) && (new File(new File(home, "bin"), "catalina.sh").exists());
portShutdown = options.get(SERVER_SHUTDOWN_PORT, tomcat ? 8005 : 4200);
portStartup = portShutdown;
command = options.get(SERVER_SHUTDOWN_COMMAND, "SHUTDOWN");
host = options.get(SERVER_SHUTDOWN_HOST, "localhost");
}
public void init(final Properties props) {
properties = props;
props.put("java.naming.factory.initial", "org.apache.openejb.client.RemoteInitialContextFactory");
final int port = options.get("ejbd.port", 4201);
props.put("java.naming.provider.url", options.get("java.naming.provider.url", "127.0.0.1:" + port));
props.put("java.naming.security.principal", "testuser");
props.put("java.naming.security.credentials", "testpassword");
}
public static void main(final String[] args) {
assert args.length > 0 : "no arguments supplied: valid arguments are 'start' or 'stop'";
if (args[0].equalsIgnoreCase(START)) {
final RemoteServer remoteServer = new RemoteServer();
try {
remoteServer.start();
} catch (final Exception e) {
remoteServer.destroy();
e.printStackTrace(System.err);
}
} else if (args[0].equalsIgnoreCase(STOP)) {
final RemoteServer remoteServer = new RemoteServer();
remoteServer.serverHasAlreadyBeenStarted = false;
try {
remoteServer.forceStop();
} catch (final Exception e) {
e.printStackTrace(System.err);
}
} else {
throw new OpenEJBRuntimeException("valid arguments are 'start' or 'stop'");
}
}
public int getPortStartup() {
return this.portStartup;
}
public void setPortStartup(final int portStartup) {
this.portStartup = portStartup;
}
public Properties getProperties() {
return this.properties;
}
public void destroy() {
try {
final boolean stopSent = stop();
final Process p = server.get();
if (p != null) {
if (stopSent) {
waitFor(p);
} else {
p.destroy();
}
}
} catch (final Exception e) {
Logger.getLogger(RemoteServer.class.getName()).log(Level.WARNING, "Failed to destroy remote server process", e);
}
}
public void start() {
start(Collections.<String>emptyList(), START, true);
}
public void start(final List<String> additionalArgs, final String cmd, final boolean checkPortAvailable) {
cmd(additionalArgs, cmd, checkPortAvailable);
}
private void cmd(final List<String> additionalArgs, final String cmd, final boolean checkPortAvailable) {
boolean ok = true;
final int port = START.equals(cmd) ? portStartup : portShutdown;
if (checkPortAvailable) {
ok = !connect(port, 1);
}
if (ok) {
try {
if (verbose) {
System.out.println("[] " + cmd.toUpperCase() + " SERVER");
}
final File home = getHome();
final String javaVersion = System.getProperty("java.version");
if (verbose) {
System.out.println("OPENEJB_HOME = " + home.getAbsolutePath());
final String systemInfo = "Java " + javaVersion + "; " + System.getProperty("os.name") + "/" + System.getProperty("os.version");
System.out.println("SYSTEM_INFO = " + systemInfo);
}
serverHasAlreadyBeenStarted = false;
final File lib = new File(home, "lib");
final File webapplib = new File(new File(new File(home, "webapps"), "tomee"), "lib");
File javaagentJar = null;
try {
javaagentJar = lib("openejb-javaagent", lib, webapplib);
} catch (final IllegalStateException ise) {
// no-op
}
final File conf = new File(home, "conf");
final File loggingProperties = new File(conf, "logging.properties");
//File openejbJar = new File(lib, "openejb-core-" + version + ".jar");
final String java;
final boolean isWindows = System.getProperty("os.name", "unknown").toLowerCase().startsWith("windows");
if (isWindows && START.equals(cmd) && options.get("server.windows.fork", false)) {
// run and forget
java = new File(System.getProperty("java.home"), "bin/javaw").getAbsolutePath();
} else {
java = new File(System.getProperty("java.home"), "bin/java").getAbsolutePath();
}
final List<String> argsList = new ArrayList<String>(20);
argsList.add(java);
argsList.add("-XX:+HeapDumpOnOutOfMemoryError");
if (debug) {
argsList.add("-Xdebug");
argsList.add("-Xnoagent");
argsList.add("-Djava.compiler=NONE");
argsList.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=" + options.get(SERVER_DEBUG_PORT, 5005));
}
if (profile) {
String yourkitHome = options.get("yourkit.home", "/Applications/YourKit_Java_Profiler_9.5.6.app/bin/mac/");
if (!yourkitHome.endsWith("/")) {
yourkitHome += "/";
}
final String yourkitOpts = options.get("yourkit.opts", "disablestacktelemetry,disableexceptiontelemetry,builtinprobes=none,delay=10000,sessionname=Tomcat");
argsList.add("-agentpath:" + yourkitHome + "libyjpagent.jnilib=" + yourkitOpts);
}
if (javaOpts != null) {
Collections.addAll(argsList, javaOpts.split(" +"));
}
final Map<String, String> addedArgs = new HashMap<String, String>();
if (additionalArgs != null) {
for (final String arg : additionalArgs) {
final String[] values = arg.split("=");
if (values.length == 1) {
addedArgs.put(values[0], "null");
} else {
addedArgs.put(values[0], values[1]);
}
argsList.add(arg);
}
}
if (!addedArgs.containsKey("-Djava.util.logging.config.file") && loggingProperties.exists()) {
argsList.add("-Djava.util.logging.config.file=" + loggingProperties.getAbsolutePath());
}
if (javaagentJar != null && javaagentJar.exists()) {
argsList.add("-javaagent:" + javaagentJar.getAbsolutePath());
}
//DMB: If you don't use an array, you get problems with jar paths containing spaces
// the command won't parse correctly
final String ps = File.pathSeparator;
final String[] args;
if (!tomcat) {
final File openejbJar = lib("openejb-core", lib, webapplib);
final StringBuilder cp = new StringBuilder(openejbJar.getAbsolutePath());
if (additionalClasspath != null) {
cp.append(ps).append(additionalClasspath);
}
argsList.add("-cp");
argsList.add(cp.toString());
argsList.add("org.apache.openejb.cli.Bootstrap");
} else {
final File bin = new File(home, "bin");
final File tlib = new File(home, "lib");
final File bootstrapJar = new File(bin, "bootstrap.jar");
final File juliJar = new File(bin, "tomcat-juli.jar");
final File commonsLoggingJar = new File(bin, "commons-logging-api.jar");
final File endorsed = new File(home, "endorsed");
final File temp = new File(home, "temp");
// if (!addedArgs.containsKey("-Dcom.sun.management.jmxremote")) {
// argsList.add("-Dcom.sun.management.jmxremote");
// }
if (!addedArgs.containsKey("-Djava.util.logging.manager")) {
argsList.add("-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager");
}
if (!addedArgs.containsKey("-Djava.io.tmpdir")) {
argsList.add("-Djava.io.tmpdir=" + temp.getAbsolutePath());
}
if (!addedArgs.containsKey("-Djava.endorsed.dirs")) {
argsList.add("-Djava.endorsed.dirs=" + endorsed.getAbsolutePath());
}
if (!addedArgs.containsKey("-Dcatalina.base")) {
argsList.add("-Dcatalina.base=" + home.getAbsolutePath());
}
if (!addedArgs.containsKey("-Dcatalina.home")) {
argsList.add("-Dcatalina.home=" + home.getAbsolutePath());
}
if (!addedArgs.containsKey("-Dcatalina.ext.dirs")) {
argsList.add("-Dcatalina.ext.dirs=" + tlib.getAbsolutePath());
}
if (!addedArgs.containsKey("-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE")) {
argsList.add("-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=true");
}
if (!addedArgs.containsKey("-Dorg.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V0")) {
argsList.add("-Dorg.apache.tomcat.util.http.ServerCookie.ALLOW_HTTP_SEPARATORS_IN_V0=true");
}
if (addedArgs.isEmpty()) { // default case
addIfSet(argsList, "javax.net.ssl.keyStore");
addIfSet(argsList, "javax.net.ssl.keyStorePassword");
addIfSet(argsList, "javax.net.ssl.trustStore");
addIfSet(argsList, "java.protocol.handler.pkgs");
}
argsList.add("-ea");
argsList.add("-classpath");
final StringBuilder cp = new StringBuilder(bootstrapJar.getAbsolutePath()).append(ps).append(juliJar.getAbsolutePath());
if (commonsLoggingJar.exists()) {
cp.append(ps).append(commonsLoggingJar.getAbsolutePath());
}
if (additionalClasspath != null) {
cp.append(ps).append(additionalClasspath);
}
argsList.add(cp.toString());
argsList.add("org.apache.catalina.startup.Bootstrap");
}
if (cmd == null) {
argsList.add(START);
} else {
argsList.add(cmd);
}
args = argsList.toArray(new String[argsList.size()]);
if (verbose) {
System.out.println(Join.join("\n", args));
}
// kill3UNIXDebug();
final ProcessBuilder pb = new ProcessBuilder(args);
pb.directory(home.getAbsoluteFile());
Process p = pb.start();
//Process p = Runtime.getRuntime().exec(args);
Pipe.pipeOut(p); // why would we need to redirect System.in to the process, TomEE doesn't use it
if (START.equals(cmd)) {
server.set(p);
} else if (STOP.equals(cmd)) {
waitFor(p);
p = server.get();
if (p != null) {
waitFor(p);
}
}
System.out.println("Started server process on port: " + port);
} catch (final Exception e) {
throw (RuntimeException) new OpenEJBRuntimeException("Cannot start the server. Exception: " + e.getClass().getName() + ": " + e.getMessage()).initCause(e);
}
if (debug) {
if (!connect(port, Integer.MAX_VALUE)) {
throw new OpenEJBRuntimeException("Could not connect to server: " + this.host + ":" + port);
}
} else {
if (!connect(port, tries)) {
throw new OpenEJBRuntimeException("Could not connect to server: " + this.host + ":" + port);
}
}
} else {
if (verbose) {
System.out.println("[] FOUND STARTED SERVER");
}
}
}
private void waitFor(final Process p) {
final CountDownLatch latch = new CountDownLatch(1);
final Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
p.waitFor();
} catch (final InterruptedException e) {
//Ignore
}
latch.countDown();
}
}, "process-waitFor");
t.start();
try {
if (!latch.await(10, TimeUnit.SECONDS)) {
killOnExit(p);
throw new RuntimeException("Timeout waiting for process");
}
} catch (final InterruptedException e) {
killOnExit(p);
}
}
public void kill3UNIX() { // debug purpose only
if (System.getProperty("os.name", "unknown").toLowerCase().startsWith("windows")) {
return;
}
try {
final Field f = server.get().getClass().getDeclaredField("pid");
f.setAccessible(true);
final int pid = (Integer) f.get(server.get());
Pipe.pipe(Runtime.getRuntime().exec("kill -3 " + pid));
} catch (final Exception e1) {
e1.printStackTrace();
}
}
private File lib(final String name, final File... dirs) {
for (final File dir : dirs) {
final File[] files = dir.listFiles();
if (files != null) {
for (final File file : files) {
if (!file.isFile()) {
continue;
}
if (!file.getName().endsWith(".jar")) {
continue;
}
if (file.getName().startsWith(name)) {
return file;
}
}
}
}
if (debug) {
for (final File dir : dirs) {
dumpLibs(dir);
}
}
throw new IllegalStateException("Cannot find the " + name + " jar");
}
// for debug purpose
private static void dumpLibs(final File dir) {
if (!dir.exists()) {
System.out.println("lib dir doesn't exist");
return;
}
final File[] files = dir.listFiles();
if (files != null) {
for (final File lib : files) {
System.out.println(lib.getAbsolutePath());
}
}
}
public Process getServer() {
return server.get();
}
private void addIfSet(final List<String> argsList, final String key) {
if (System.getProperties().containsKey(key)) {
argsList.add("-D" + key + "=" + System.getProperty(key));
}
}
private File getHome() {
if (home != null) {
return home;
}
final String openejbHome = System.getProperty("openejb.home");
if (openejbHome != null) {
home = new File(openejbHome);
}
return home;
}
public boolean stop() {
if (sendShutdown(5)) {
return true;
} else {
if (verbose) {
notSent();
}
return false;
}
}
private void notSent() {
System.out.println("Failed to send the shutdown notification - TomEE is likely shut down already");
}
public void forceStop() throws Exception {
if (sendShutdown(5)) {
// Check TomEE was effectively shut down after getting the message
// There can be concurrent shutdown operations (catalina shutdown hook for instance),
// so we have to wait here since it is important to be synchronous in this method
waitForServerShutdown();
} else {
if (verbose) {
notSent();
}
}
}
private void waitForServerShutdown() throws Exception {
if (verbose) {
System.out.print("Waiting for TomEE shutdown.");
}
final boolean b = disconnect(portShutdown, tries);
if (verbose) {
System.out.println();
}
if (!b) {
//We need to know about this
System.out.println("SEVERE: Failed to shutdown TomEE running on port " + portStartup + " using shutdown port: " + portShutdown);
}
}
/**
* Send the shutdown message to the running server
*
* @param attempts How many times to try to send the message before giving up
* @return True is the message was sent, else false if unable to connect after the defined number of attempts
*/
private boolean sendShutdown(int attempts) {
Socket socket = null;
OutputStream stream = null;
try {
socket = new Socket(host, portShutdown);
stream = socket.getOutputStream();
final String shutdown = command + Character.toString((char) 0);
for (int i = 0; i < shutdown.length(); i++) {
stream.write(shutdown.charAt(i));
}
stream.flush();
} catch (final Exception e) {
if (attempts > 0) {
try {
Thread.sleep(1000);
} catch (final InterruptedException ie) {
return false;
}
return sendShutdown(--attempts);
} else {
return false;
}
} finally {
if (null != stream) {
try {
stream.close();
} catch (final Exception e) {
// Ignore
}
}
if (socket != null) {
try {
socket.close();
} catch (final Exception e) {
// Ignore
}
}
}
return true;
}
private boolean connect(final int port, int tries) {
if (verbose) {
System.out.println("[] CONNECT ATTEMPT " + (this.tries - tries) + " on port: " + port);
}
Socket s = null;
try {
s = new Socket();
s.connect(new InetSocketAddress(this.host, port), 1000);
s.getOutputStream().close();
if (verbose) {
System.out.println("[] CONNECTED IN " + (this.tries - tries));
}
} catch (final Exception e) {
if (tries < 2) {
if (verbose) {
System.out.println("[] CONNECT ATTEMPTS FAILED ( " + (this.tries - tries) + " ATTEMPTS)");
}
return false;
} else {
try {
Thread.sleep(1000);
} catch (final Exception e2) {
e2.printStackTrace();
}
return connect(port, --tries);
}
} finally {
if (s != null) {
try {
s.close();
} catch (final Exception ignored) {
// no-op
}
}
}
return true;
}
private boolean disconnect(final int port, int tries) {
if (verbose) {
System.out.println("[] DISCONNECT ATTEMPT " + (this.tries - tries) + " on port: " + port);
}
Socket s = null;
try {
s = new Socket();
s.connect(new InetSocketAddress(this.host, port), 500);
s.getOutputStream().close();
if (verbose) {
System.out.println("[] NOT DISCONNECTED AFTER ( " + (this.tries - tries) + " ATTEMPTS)");
}
if (tries < 2) {
//Give up
return false;
} else {
try {
Thread.sleep(1000);
} catch (final Exception e2) {
e2.printStackTrace();
}
return disconnect(port, --tries);
}
} catch (final IOException e) {
//This is what we want
} finally {
if (s != null) {
try {
s.close();
} catch (final Exception ignored) {
// no-op
}
}
}
return true;
}
public void setAdditionalClasspath(final String additionalClasspath) {
this.additionalClasspath = additionalClasspath;
}
public void killOnExit() {
final Process p = this.server.get();
if (!serverHasAlreadyBeenStarted && kill.contains(p)) {
return;
}
killOnExit(p);
}
private static void killOnExit(final Process p) {
kill.add(p);
}
// Shutdown hook for processes
private static final List<Process> kill = new ArrayList<Process>();
static {
Runtime.getRuntime().addShutdownHook(new CleanUpThread());
}
public static class CleanUpThread extends Thread {
@Override
public void run() {
for (final Process server : kill) {
try {
if (server != null) {
server.destroy();
server.waitFor();
}
} catch (final Throwable e) {
//Ignore
}
}
}
}
}