/*
* Copyright 2012 Harald Wellmann
*
* 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.ops4j.pax.exam.glassfish.embedded;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.glassfish.embeddable.Deployer;
import org.glassfish.embeddable.GlassFish;
import org.glassfish.embeddable.GlassFishException;
import org.glassfish.embeddable.GlassFishProperties;
import org.glassfish.embeddable.GlassFishRuntime;
import org.ops4j.io.StreamUtils;
import org.ops4j.pax.exam.ConfigurationManager;
import org.ops4j.pax.exam.ExamSystem;
import org.ops4j.pax.exam.ProbeInvoker;
import org.ops4j.pax.exam.ProbeInvokerFactory;
import org.ops4j.pax.exam.TestAddress;
import org.ops4j.pax.exam.TestContainer;
import org.ops4j.pax.exam.TestContainerException;
import org.ops4j.pax.exam.TestDirectory;
import org.ops4j.pax.exam.TestInstantiationInstruction;
import org.ops4j.pax.exam.options.UrlDeploymentOption;
import org.ops4j.pax.exam.options.WarProbeOption;
import org.ops4j.spi.ServiceProviderFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* A {@link TestContainer} for the GlassFish 3.1 Java EE 6 application server.
* <p>
* This container uses Embedded GlassFish and only Java EE mode, but not OSGi mode. You deploy WAR
* modules via Pax Exam options (but no OSGi bundles).
* <p>
* The test probe is a WAR built on the fly from the classpath contents with some default
* exclusions.
* <p>
* GlassFish logging is redirected from java.util.logging to SLF4J. The necessary artifacts are
* provisioned by this container automatically.
*
* @author Harald Wellmann
* @since 3.0.0
*/
public class EmbeddedGlassFishTestContainer implements TestContainer {
/**
* Configuration property key for GlassFish installation configuration file directory. The files
* contained in this directory will be copied to the config directory of the GlassFish instance.
*/
public static final String GLASSFISH_CONFIG_DIR_KEY = "pax.exam.glassfish.config.dir";
private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGlassFishTestContainer.class);
/**
* Name of the probe web application (in Java EE mode).
*/
private static final String PROBE_APPLICATION_NAME = "Pax-Exam-Probe";
/**
* XPath to read the HTTP port from the domain.xml configuration file.
*/
private static final String HTTP_PORT_XPATH = "/domain/configs/config/network-config/network-listeners/network-listener[@name='http-listener-1']/@port";
/**
* Stack of deployed modules. On shutdown, the modules are undeployed in reverse order.
*/
private Stack<String> deployed = new Stack<String>();
private String warProbe;
/**
* Pax Exam system with configuration options.
*/
private ExamSystem system;
/**
* GlassFish OSGi service.
*/
private GlassFish glassFish;
/**
* Test directory which tracks all tests in the current suite. We need to register the context
* URL of the probe web app as access point.
*/
private TestDirectory testDirectory;
private String configDirName;
/**
* Creates a GlassFish container, running on top of an OSGi framework.
*
* @param system
* Pax Exam system configuration
*/
public EmbeddedGlassFishTestContainer(ExamSystem system) {
this.system = system;
this.testDirectory = TestDirectory.getInstance();
}
/**
* Calls a test with the given address. In Java EE mode, we lookup the test from the test
* directory and invoke it via probe invoker obtained from the Java SE service loader. (This
* invoker uses a servlet bridge,)
*/
public synchronized void call(TestAddress address) {
TestInstantiationInstruction instruction = testDirectory.lookup(address);
ProbeInvokerFactory probeInvokerFactory = ServiceProviderFinder
.loadUniqueServiceProvider(ProbeInvokerFactory.class);
ProbeInvoker invoker = probeInvokerFactory.createProbeInvoker(null, instruction.toString());
invoker.call(address.arguments());
}
/**
* Installs a probe in the test container.
* <p>
* In Java EE mode, the probe is a WAR, enriched by the Pax Exam servlet bridge which allows us
* to invoke tests running within the container via an HTTP client.
*
* @param location
* bundle location, not used for WAR probes
* @param stream
* input stream containing probe
* @return bundle ID, or -1 for WAR
*/
public synchronized long install(String location, InputStream stream) {
try {
// just make sure we don't get an "option not recognized" warning
system.getOptions(WarProbeOption.class);
LOG.info("deploying probe");
Deployer deployer = glassFish.getDeployer();
/*
* FIXME The following should work, but does not. For some reason, we cannot directly
* deploy from a stream. As a workaround, we copy the stream to a temp file and deploy
* the file.
*
* deployer.deploy( stream, "--name", "Pax-Exam-Probe", "--contextroot",
* "Pax-Exam-Probe" );
*/
File tempFile = File.createTempFile("pax-exam", ".war");
tempFile.deleteOnExit();
StreamUtils.copyStream(stream, new FileOutputStream(tempFile), true);
deployer.deploy(tempFile, "--name", PROBE_APPLICATION_NAME, "--contextroot",
PROBE_APPLICATION_NAME);
deployed.push(PROBE_APPLICATION_NAME);
}
catch (GlassFishException exc) {
throw new TestContainerException(exc);
}
catch (IOException exc) {
throw new TestContainerException(exc);
}
return -1;
}
public synchronized long install(InputStream stream) {
return install("local", stream);
}
/**
* Deploys all Java EE modules defined in Pax Exam options. For options without an explicit
* application name, names app1, app2 etc. are generated on the fly. The context root defaults
* to the application name if not set in the option.
*/
public void deployModules() {
UrlDeploymentOption[] deploymentOptions = system.getOptions(UrlDeploymentOption.class);
int numModules = 0;
for (UrlDeploymentOption option : deploymentOptions) {
numModules++;
if (option.getName() == null) {
option.name("app" + numModules);
}
deployModule(option);
}
}
/**
* Deploys the module specified by the given option.
*
* @param option
* deployment option
*/
private void deployModule(UrlDeploymentOption option) {
try {
String url = option.getURL();
LOG.info("deploying module {}", url);
URI uri = new URL(url).toURI();
String applicationName = option.getName();
String contextRoot = option.getContextRoot();
if (contextRoot == null) {
contextRoot = applicationName;
}
Deployer deployer = glassFish.getDeployer();
deployer.deploy(uri, "--name", applicationName, "--contextroot", applicationName);
deployed.push(applicationName);
LOG.info("deployed module {}", url);
}
catch (IOException exc) {
throw new TestContainerException(exc);
}
catch (GlassFishException exc) {
throw new TestContainerException(exc);
}
catch (URISyntaxException exc) {
throw new TestContainerException(exc);
}
}
/**
* Undeploys all modules and shuts down the GlassFish runtime.
*/
public synchronized void cleanup() {
undeployModules();
try {
glassFish.stop();
}
catch (GlassFishException exc) {
throw new TestContainerException(exc);
}
}
/**
* Undeploys all deployed modules in reverse order.
*/
private void undeployModules() {
try {
Deployer deployer = glassFish.getDeployer();
while (!deployed.isEmpty()) {
String applicationName = deployed.pop();
deployer.undeploy(applicationName);
}
}
catch (GlassFishException exc) {
throw new TestContainerException(exc);
}
}
/**
* Starts the GlassFish container.
*/
public TestContainer start() {
System.setProperty("java.protocol.handler.pkgs", "org.ops4j.pax.url");
ConfigurationManager cm = new ConfigurationManager();
configDirName = cm.getProperty(GLASSFISH_CONFIG_DIR_KEY,
"src/test/resources/glassfish-config");
File domainConfig = new File(configDirName, "domain.xml");
GlassFishProperties gfProps = new GlassFishProperties();
if (domainConfig.exists()) {
gfProps.setConfigFileURI(domainConfig.toURI().toString());
}
try {
glassFish = GlassFishRuntime.bootstrap().newGlassFish(gfProps);
glassFish.start();
// set access point in test directory
String portNumber = getPortNumber(domainConfig);
testDirectory.setAccessPoint(new URI("http://localhost:" + portNumber
+ "/Pax-Exam-Probe/"));
deployModules();
}
catch (GlassFishException e) {
throw new TestContainerException("Problem starting test container.", e);
}
catch (URISyntaxException e) {
throw new TestContainerException("Problem starting test container.", e);
}
return this;
}
/**
* Reads the first port number from the domain.xml configuration.
*
* @param domainConfig
* @return port number as string
*/
private String getPortNumber(File domainConfig) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
builder = factory.newDocumentBuilder();
Document doc = builder.parse(domainConfig);
XPathFactory xpf = XPathFactory.newInstance();
XPath xPath = xpf.newXPath();
String port = xPath.evaluate(HTTP_PORT_XPATH, doc);
return port;
}
catch (ParserConfigurationException exc) {
throw new IllegalArgumentException(exc);
}
catch (SAXException exc) {
throw new IllegalArgumentException(exc);
}
catch (IOException exc) {
throw new IllegalArgumentException(exc);
}
catch (XPathExpressionException exc) {
throw new IllegalArgumentException(exc);
}
}
/**
* Stops the test container gracefully, undeploying all modules and uninstalling all bundles.
*/
public TestContainer stop() {
if (glassFish != null) {
cleanup();
system.clear();
}
else {
LOG.warn("Framework does not exist. Called start() before ? ");
}
return this;
}
@Override
public String toString() {
return "EmbeddedGlassFish";
}
@Override
public long installProbe(InputStream stream) {
install(stream);
this.warProbe = deployed.pop();
return -1;
}
@Override
public void uninstallProbe() {
try {
glassFish.getDeployer().undeploy(warProbe);
this.warProbe = null;
}
catch (GlassFishException exc) {
throw new TestContainerException(exc);
}
}
}