package org.keycloak.testsuite.arquillian.karaf;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import org.jboss.arquillian.container.osgi.jmx.JMXDeployableContainer;
import org.jboss.arquillian.container.osgi.jmx.ObjectNameFactory;
import org.jboss.arquillian.container.osgi.karaf.managed.KarafManagedContainerConfiguration;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.osgi.jmx.framework.BundleStateMBean;
import org.osgi.jmx.framework.FrameworkMBean;
import org.osgi.jmx.framework.ServiceStateMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* KarafManagedDeployableContainer
*
* @author thomas.diesler@jboss.com
*/
public class CustomKarafContainer<T extends KarafManagedContainerConfiguration> extends JMXDeployableContainer<T> {
static final Logger _logger = LoggerFactory.getLogger(CustomKarafContainer.class.getPackage().getName());
private KarafManagedContainerConfiguration config;
private Process process;
@Override
public Class<T> getConfigurationClass() {
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) KarafManagedContainerConfiguration.class;
return clazz;
}
@Override
public void setup(T config) {
super.setup(config);
this.config = config;
}
@Override
public void start() throws LifecycleException {
// Try to connect to an already running server
MBeanServerConnection mbeanServer = null;
try {
mbeanServer = getMBeanServerConnection(500, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
// ignore
}
if (mbeanServer != null && !config.isAllowConnectingToRunningServer()) {
throw new LifecycleException(
"The server is already running! Managed containers does not support connecting to running server instances due to the " +
"possible harmful effect of connecting to the wrong server. Please stop server before running or change to another type of container.\n" +
"To disable this check and allow Arquillian to connect to a running server, set allowConnectingToRunningServer to true in the container configuration");
}
// Start the Karaf process
if (mbeanServer == null) {
String karafHome = config.getKarafHome();
if (karafHome == null)
throw new IllegalStateException("karafHome cannot be null");
File karafHomeDir = new File(karafHome).getAbsoluteFile();
if (!karafHomeDir.isDirectory())
throw new IllegalStateException("Not a valid Karaf home dir: " + karafHomeDir);
String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
_logger.info(String.format("Using java: %s", java));
List<String> cmd = new ArrayList<String>();
cmd.add(java);
// JavaVM args
String javaArgs = config.getJavaVmArguments();
if (!javaArgs.contains("-Xmx")) {
javaArgs = KarafManagedContainerConfiguration.DEFAULT_JAVAVM_ARGUMENTS + " " + javaArgs;
}
cmd.addAll(Arrays.asList(javaArgs.split("\\s")));
// Karaf properties
cmd.add("-Dkaraf.home=" + karafHomeDir);
cmd.add("-Dkaraf.base=" + karafHomeDir);
cmd.add("-Dkaraf.etc=" + karafHomeDir + "/etc");
cmd.add("-Dkaraf.data=" + karafHomeDir + "/data");
cmd.add("-Dkaraf.instances=" + karafHomeDir + "/instances");
cmd.add("-Dkaraf.startLocalConsole=false");
cmd.add("-Dkaraf.startRemoteShell=true");
// Java properties
cmd.add("-Djava.io.tmpdir=" + new File(karafHomeDir, "data/tmp"));
cmd.add("-Djava.util.logging.config.file=" + new File(karafHomeDir, "etc/java.util.logging.properties"));
cmd.add("-Djava.endorsed.dirs=" + new File(karafHomeDir, "lib/endorsed"));
// Classpath
StringBuilder classPath = new StringBuilder();
File karafLibDir = new File(karafHomeDir, "lib");
String[] libs = karafLibDir.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith("karaf");
}
});
for (String lib : libs) {
String separator = classPath.length() > 0 ? File.pathSeparator : "";
classPath.append(separator).append(new File(karafHomeDir, "lib/" + lib));
}
cmd.add("-classpath");
cmd.add(classPath.toString());
// Main class
cmd.add("org.apache.karaf.main.Main");
// Output the startup command
StringBuffer cmdstr = new StringBuffer();
for (String tok : cmd) {
cmdstr.append(tok).append(" ");
}
_logger.debug("Starting Karaf with: {}", cmdstr);
try {
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
processBuilder.directory(karafHomeDir);
processBuilder.redirectErrorStream(true);
process = processBuilder.start();
new Thread(new ConsoleConsumer()).start();
} catch (Exception ex) {
throw new LifecycleException("Cannot start managed Karaf container", ex);
}
// Get the MBeanServerConnection
try {
mbeanServer = getMBeanServerConnection(30, TimeUnit.SECONDS);
} catch (Exception ex) {
destroyKarafProcess();
throw new LifecycleException("Cannot obtain MBean server connection", ex);
}
}
mbeanServerInstance.set(mbeanServer);
try {
// Get the FrameworkMBean
ObjectName oname = ObjectNameFactory.create("osgi.core:type=framework,*");
frameworkMBean = getMBeanProxy(mbeanServer, oname, FrameworkMBean.class, 30, TimeUnit.SECONDS);
// Get the BundleStateMBean
oname = ObjectNameFactory.create("osgi.core:type=bundleState,*");
bundleStateMBean = getMBeanProxy(mbeanServer, oname, BundleStateMBean.class, 30, TimeUnit.SECONDS);
// Get the BundleStateMBean
oname = ObjectNameFactory.create("osgi.core:type=serviceState,*");
serviceStateMBean = getMBeanProxy(mbeanServer, oname, ServiceStateMBean.class, 30, TimeUnit.SECONDS);
// Install the arquillian bundle to become active
installArquillianBundle();
// Await the arquillian bundle to become active
awaitArquillianBundleActive(30, TimeUnit.SECONDS);
// Await the beginning start level
Integer beginningStartLevel = config.getKarafBeginningStartLevel();
if (beginningStartLevel != null)
awaitBeginningStartLevel(beginningStartLevel, 30, TimeUnit.SECONDS);
// Await bootsrap complete services
awaitBootstrapCompleteServices();
} catch (RuntimeException rte) {
destroyKarafProcess();
throw rte;
} catch (Exception ex) {
destroyKarafProcess();
throw new LifecycleException("Cannot start Karaf container", ex);
}
}
@Override
public void stop() throws LifecycleException {
super.stop();
destroyKarafProcess();
}
private void destroyKarafProcess() throws LifecycleException {
if (process != null) {
process.destroy();
try {
process.waitFor();
} catch (InterruptedException e) {
throw new LifecycleException("Cannot start Karaf container", e);
}
}
}
/**
* Runnable that consumes the output of the process. If nothing consumes the output the AS will hang on some platforms
*
* @author Stuart Douglas
*/
private class ConsoleConsumer implements Runnable {
@Override
public void run() {
final InputStream stream = process.getInputStream();
final boolean writeOutput = config.isOutputToConsole();
try {
byte[] buf = new byte[32];
int num;
// Do not try reading a line cos it considers '\r' end of line
while ((num = stream.read(buf)) != -1) {
if (writeOutput)
System.out.write(buf, 0, num);
}
} catch (IOException ignored) {
}
}
}
}