/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.arquillian.daemon.container.managed;
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.net.InetSocketAddress;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.daemon.container.common.DaemonDeployableContainerBase;
import org.jboss.arquillian.daemon.protocol.wire.WireProtocol;
/**
* {@link DeployableContainer} implementation for Managed Arquillian Server Daemon (handles start/stop of the server as
* part of lifecycle).
*
* @author <a href="mailto:alr@jboss.org">Andrew Lee Rubinger</a>
*/
public class ManagedDaemonDeployableContainer extends
DaemonDeployableContainerBase<ManagedDaemonContainerConfiguration> implements
DeployableContainer<ManagedDaemonContainerConfiguration> {
private static final Logger log = Logger.getLogger(ManagedDaemonDeployableContainer.class.getName());
private static final String SYSPROP_KEY_JAVA_HOME = "java.home";
private Thread shutdownHookThread;
private File serverjarFile;
private Process remoteProcess;
/**
* {@inheritDoc}
*
* @see org.jboss.arquillian.container.spi.client.container.DeployableContainer#getConfigurationClass()
*/
@Override
public Class<ManagedDaemonContainerConfiguration> getConfigurationClass() {
return ManagedDaemonContainerConfiguration.class;
}
/**
* {@inheritDoc}
*
* @see org.jboss.arquillian.daemon.container.common.DaemonDeployableContainerBase#setup(org.jboss.arquillian.daemon.container.common.DaemonContainerConfigurationBase)
*/
@Override
public void setup(final ManagedDaemonContainerConfiguration configuration) {
super.setup(configuration);
serverjarFile = new File(configuration.getServerJarFile());
}
/**
* Starts the process, then forwards control to {@link DaemonDeployableContainerBase#start()} to connect.
*
* @see org.jboss.arquillian.daemon.container.common.DaemonDeployableContainerBase#start()
*/
@Override
public void start() throws LifecycleException {
// Build the launch command
final File javaHome = new File(SecurityActions.getSystemProperty(SYSPROP_KEY_JAVA_HOME));
final List<String> command = new ArrayList<String>(10);
command.add(javaHome.getAbsolutePath() + "/bin/java");
command.add("-jar");
command.add(serverjarFile.getAbsolutePath());
final InetSocketAddress remoteAddress = this.getRemoteAddress();
command.add(remoteAddress.getHostString());
command.add(Integer.toString(remoteAddress.getPort()));
// Launch the process
final ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(Redirect.INHERIT);
final Process process;
try {
process = processBuilder.start();
this.remoteProcess = process;
} catch (final IOException e) {
throw new LifecycleException("Could not start container", e);
}
// Add a shutdown hook for when this current process terminates to kill the one we've launched
final Runnable shutdownServerRunnable = new Runnable() {
@Override
public void run() {
if (process != null) {
process.destroy();
try {
process.waitFor();
} catch (final InterruptedException e) {
Thread.interrupted();
throw new RuntimeException("Interrupted while awaiting server daemon process termination", e);
}
}
}
};
shutdownHookThread = new Thread(shutdownServerRunnable);
Runtime.getRuntime().addShutdownHook(shutdownHookThread);
// Call the super implementation (to handle connect)
super.start();
}
/**
* {@inheritDoc}
*
* @see org.jboss.arquillian.daemon.container.common.DaemonDeployableContainerBase#stop()
*/
@Override
public void stop() throws LifecycleException {
try {
// Write the stop command
this.getWriter().print(WireProtocol.COMMAND_STOP);
// Terminate the command and flush
this.getWriter().print(WireProtocol.COMMAND_EOF_DELIMITER);
this.getWriter().flush();
// Block until we get "OK" response
final String response = this.getReader().readLine();
if (log.isLoggable(Level.FINEST)) {
log.finest("Response from stop: " + response);
}
log.info("Response from stop: " + response);
} catch (final IOException ioe) {
throw new LifecycleException(
"Unexpected problem encountered during read of the response from stop request", ioe);
} catch (final RuntimeException re) {
throw new LifecycleException("Unexpected problem encountered during stop", re);
} finally {
// Call super implementation to close up resources
super.stop();
}
// Block until the process is killed
try {
remoteProcess.waitFor();
} catch (final InterruptedException ie) {
Thread.interrupted();
}
// Null out
remoteProcess = null;
}
private static final class SecurityActions {
private SecurityActions() {
throw new UnsupportedOperationException("No instance permitted");
}
static String getSystemProperty(final String key) {
assert key != null && key.length() > 0 : "key must be specified";
if (System.getSecurityManager() == null) {
return System.getProperty(key);
}
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty(key);
}
});
}
}
}