blob: 51971dec1dfa73479c28595c0cca8104a8352013 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 VMware Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VMware Inc. - initial contribution
*******************************************************************************/
package org.eclipse.virgo.web.test;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.eclipse.virgo.nano.deployer.api.core.ApplicationDeployer;
import org.eclipse.virgo.nano.deployer.api.core.DeploymentIdentity;
import org.eclipse.virgo.nano.deployer.api.core.DeploymentOptions;
import org.eclipse.virgo.kernel.model.management.ManageableArtifact;
import org.eclipse.virgo.kernel.osgi.framework.OsgiFramework;
import org.eclipse.virgo.test.framework.dmkernel.DmKernelTestRunner;
import org.eclipse.virgo.util.io.PathReference;
/**
* Abstract base class for all web integration tests. Transparently retrieves the {@link ApplicationDeployer} from the
* OSGi service registry and provides support for subclasses to deploy web applications (i.e., WARs, hybrid WARs, and
* PARs which contain a web module). In addition, simple assertion facilities are provided for verifying that a
* particular web resource is available (e.g., via an HTTP GET or POST request).
*
*/
@RunWith(DmKernelTestRunner.class)
public abstract class AbstractWebIntegrationTests {
private static final long HOT_DEPLOY_TIMEOUT = 30000;
private static final long WEB_PLAN_DEPLOY_TIMEOUT = 5*60*1000; // 5 minutes
private static final String CURRENT_VERSION = "3.7.0";
private static final String USER_REGION_NAME = "org.eclipse.virgo.region.user";
protected final List<String> deployedWebApps = new ArrayList<String>();
protected OsgiFramework osgiFramework;
protected ApplicationDeployer appDeployer;
protected boolean reuseHttpClient = true;
private final HttpClient httpClient = new HttpClient();
protected boolean followRedirects = true;
/**
* Gets the {@link HttpClient} to use for the current request. If {@link #reuseHttpClient} is set to
* <code>true</code>, this method will return the same, pre-instantiated client; otherwise, this method will
* instantiate and return a new client.
*/
protected HttpClient getHttpClient() {
return reuseHttpClient ? this.httpClient : new HttpClient();
}
/**
* @param context the context path of the web-app, which if non-null, will be prepended to the resource path
* @param resource the resource path to test against
* @param expectedResponseCode the expected HTTP response code
*/
protected void assertGetRequest(String context, String resource, int expectedResponseCode) throws Exception {
assertGetRequest(context, resource, expectedResponseCode, (List<String>) null);
}
/**
* @param context the context path of the web-app, which if non-null, will be prepended to the resource path
* @param resource the resource path to test against
* @param expectedResponseCode the expected HTTP response code
* @param expectedContents text expected to exist in the returned resource
*/
protected void assertGetRequest(String context, String resource, int expectedResponseCode, List<String> expectedContents) throws Exception {
assertGetRequest(context, resource, this.followRedirects, expectedResponseCode, expectedContents);
}
/**
* @param context the context path of the web-app, which if non-null, will be prepended to the resource path
* @param resource the resource path to test against
* @param followRedirects whether or not to automatically follow redirects
* @param expectedResponseCode the expected HTTP response code
* @param expectedContents list of text strings expected to exist in the returned resource
*/
protected void assertGetRequest(String context, String resource, boolean followRedirects, int expectedResponseCode, List<String> expectedContents)
throws Exception {
assertGetRequest("http://localhost:8080/" + (context == null ? "" : context + "/") + resource, followRedirects, expectedResponseCode,
expectedContents);
}
/**
* @param address the complete address (i.e., URL) against which to test
* @param expectedResponseCode the expected HTTP response code
* @param expectedContents list of text strings expected to exist in the returned resource
*/
protected void assertGetRequest(String address, int expectedResponseCode, List<String> expectedContents) throws Exception {
assertGetRequest(address, this.followRedirects, expectedResponseCode, expectedContents);
}
/**
* @param address the complete address (i.e., URL) against which to test
* @param followRedirects whether or not to automatically follow redirects
* @param expectedResponseCode the expected HTTP response code
* @param expectedContents list of text strings expected to exist in the returned resource
*/
protected void assertGetRequest(String address, boolean followRedirects, int expectedResponseCode, List<String> expectedContents)
throws Exception {
System.out.println("AbstractWebIntegrationTests: executing GET request for [" + address + "].");
GetMethod get = new GetMethod(address);
get.setFollowRedirects(followRedirects);
int responseCode = getHttpClient().executeMethod(get);
System.out.println(get.getResponseBodyAsString());
assertEquals("Verifying HTTP response code for URL [" + address + "]", expectedResponseCode, responseCode);
if (responseCode / 100 == 2 && expectedContents != null) {
String body = get.getResponseBodyAsString();
System.out.println(body);
assertNotNull("The response body for URL [" + address + "] should not be null.", body);
// System.err.println(body);
for (String expected : expectedContents) {
assertTrue("The response body for URL [" + address + "] should contain [" + expected + "].", body.contains(expected));
}
}
}
/**
* @param context the context path of the web-app, which if non-null, will be prepended to the resource path
* @param resource the resource path to test against
* @param params parameters in the form of name-value pairs to be passed in the initial POST request
* @param followRedirectsForGet whether or not to automatically follow redirects for a GET request following the
* initial POST request
* @param expectedPostResponseCode the expected HTTP response code for the initial POST request
* @param expectedGetAfterPostResponseCode the expected HTTP response code for the subsequent GET request
* @param expectedContents text expected to exist in the returned resource
*/
protected void assertPostRequest(String context, String resource, Map<String, String> params, boolean followRedirectsForGet,
int expectedPostResponseCode, int expectedGetAfterPostResponseCode, String expectedContents) throws Exception {
final String address = "http://localhost:8080/" + (context == null ? "" : context + "/") + resource;
System.out.println("AbstractWebIntegrationTests: executing POST request for [" + address + "].");
final PostMethod post = new PostMethod(address);
for (String name : params.keySet()) {
post.setParameter(name, params.get(name));
}
int responseCode = getHttpClient().executeMethod(post);
assertEquals("Verifying HTTP POST response code for URL [" + address + "]", expectedPostResponseCode, responseCode);
if (responseCode / 100 == 2 && expectedContents != null) {
String body = post.getResponseBodyAsString();
assertNotNull("The response body for URL [" + address + "] should not be null.", body);
// System.err.println(body);
assertTrue("The response body for URL [" + address + "] should contain [" + expectedContents + "].", body.contains(expectedContents));
} else if (responseCode == SC_FOUND && expectedContents != null) {
String location = post.getResponseHeader("Location").getValue();
assertGetRequest(location, followRedirectsForGet, expectedGetAfterPostResponseCode, Arrays.asList(expectedContents));
}
}
/**
* Verifies only the existence of the supplied resources (i.e., response code 200); does <b>not</b> check contents
* of response body.
*
* @param context the context path of the web-app
* @param file the file from which to deploy the web-app
* @param resources list of resources to check
* @see #assertDeployAndUndeployBehavior(String, URI, String...)
*/
protected void assertDeployAndUndeployBehavior(String context, File file, String... resources) throws Exception {
assertDeployAndUndeployBehavior(context, file.toURI(), resources);
}
/**
* Verifies only the existence of the supplied resources (i.e., response code 200); does <b>not</b> check contents
* of response body.
*
* @param context the context path of the web-app
* @param uri the URI from which to deploy the web-app
* @param resources list of resources to check
* @see #assertDeployAndUndeployBehavior(String, File, Map)
*/
protected void assertDeployAndUndeployBehavior(String context, URI uri, String... resources) throws Exception {
DeploymentIdentity deploymentIdentity = this.appDeployer.deploy(uri, new DeploymentOptions(false, false, true));
this.deployedWebApps.add(context);
// Uncomment if you'd like to pause the test and view the results in a web browser.
//System.in.read();
try {
for (String resource : resources) {
assertGetRequest(context, resource, SC_OK, null);
}
} finally {
this.appDeployer.undeploy(deploymentIdentity);
}
for (String resource : resources) {
assertGetRequest(context, resource, SC_NOT_FOUND);
}
}
/**
* Verifies the existence of the supplied resources (i.e., response code 200) and checks the contents of
* corresponding response bodies.
*
* @param context the context path of the web-app
* @param file the file from which to deploy the web-app
* @param expectations a map of expected contents per resource, keyed by resource path.
*/
protected void assertDeployAndUndeployBehavior(String context, File file, Map<String, List<String>> expectations) throws Exception {
final URI uri = file.toURI();
DeploymentIdentity deploymentIdentity = this.appDeployer.deploy(uri);
this.deployedWebApps.add(context);
// Uncomment if you'd like to pause the test and view the results in a web browser.
// System.in.read();
try {
for (String resource : expectations.keySet()) {
List<String> expectedContents = expectations.get(resource);
assertGetRequest(context, resource, SC_OK, expectedContents);
}
} finally {
this.appDeployer.undeploy(deploymentIdentity);
}
for (String resource : expectations.keySet()) {
assertGetRequest(context, resource, SC_NOT_FOUND, null);
}
}
/**
* @param context the context path of the web-app
* @param file the file from which to deploy the web-app
* @return the {@link DeploymentIdentity} of the deployed application
*/
protected DeploymentIdentity assertDeployBehavior(String context, File file) throws Exception {
return assertDeployBehavior(context, file, new HashMap<String, List<String>>());
}
/**
* @param context the context path of the web-app
* @param file the file from which to deploy the web-app
* @param expectations a map of expected contents per resource, keyed by resource path.
* @return the {@link DeploymentIdentity} of the deployed application
*/
protected DeploymentIdentity assertDeployBehavior(String context, File file, Map<String, List<String>> expectations) throws Exception {
final URI uri = file.toURI();
// Deploy non-recoverably and not owned by the deployer. Non-recoverable should speed up the tests.
DeploymentIdentity deploymentIdentity = this.appDeployer.deploy(uri, new DeploymentOptions(false, false, true));
this.deployedWebApps.add(context);
// Uncomment if you'd like to pause the test and view the results in a web browser.
// System.in.read();
for (String resource : expectations.keySet()) {
List<String> expectedContents = expectations.get(resource);
assertGetRequest(context, resource, SC_OK, expectedContents);
}
return deploymentIdentity;
}
/**
* @param moduleSymbolicName the bundle symbolic name of the module to refresh
* @param context the context path of the web-app
* @param file the file from which to refresh the web-app
* @param expectations a map of expected contents per resource, keyed by resource path.
*/
protected void assertRefreshBehavior(String moduleSymbolicName, String context, File file, Map<String, List<String>> expectations)
throws Exception {
final URI uri = file.toURI();
this.appDeployer.refresh(uri, moduleSymbolicName);
// Uncomment if you'd like to pause the test and view the results in a web browser.
// System.in.read();
for (String resource : expectations.keySet()) {
List<String> expectedContents = expectations.get(resource);
assertGetRequest(context, resource, SC_OK, expectedContents);
}
}
/**
* @param context the context path of the web-app
* @param deploymentIdentity the {@link DeploymentIdentity} of the deployed web-app
*/
protected void assertUndeployBehavior(String context, DeploymentIdentity deploymentIdentity) throws Exception {
assertUndeployBehavior(context, deploymentIdentity, new HashMap<String, List<String>>());
}
/**
* @param context the context path of the web-app
* @param deploymentIdentity the {@link DeploymentIdentity} of the deployed web-app
* @param expectations a map of expected contents per resource, keyed by resource path.
*/
protected void assertUndeployBehavior(String context, DeploymentIdentity deploymentIdentity, Map<String, List<String>> expectations)
throws Exception {
this.appDeployer.undeploy(deploymentIdentity);
for (String resource : expectations.keySet()) {
assertGetRequest(context, resource, SC_BAD_REQUEST, null);
}
}
@Before
public void setUp() throws Exception {
awaitInitialArtifactDeployment();
BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceReference<ApplicationDeployer> appDeployerServiceReference = bundleContext.getServiceReference(ApplicationDeployer.class);
assertNotNull("ApplicationDeployer service reference not found", appDeployerServiceReference);
this.appDeployer = bundleContext.getService(appDeployerServiceReference);
assertNotNull("ApplicationDeployer service not found", this.appDeployer);
}
@AfterClass
public static void cleanup() throws Exception {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=plan,name=org.eclipse.virgo.web.tomcat,version=" + CURRENT_VERSION + ",region=global");
try {
mBeanServer.invoke(objectName, "stop", null, null);
mBeanServer.invoke(objectName, "uninstall", null, null);
} catch (JMException _) {
}
}
private void awaitInitialArtifactDeployment() throws JMException, InterruptedException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=plan,name=org.eclipse.virgo.web.tomcat,version=" + CURRENT_VERSION + ",region=global");
Object state = null;
long startTime = System.currentTimeMillis();
while (!"ACTIVE".equals(state)) {
try {
state = mBeanServer.getAttribute(objectName, "State");
Thread.sleep(100);
} catch (InstanceNotFoundException _) {
}
if (System.currentTimeMillis() - startTime > WEB_PLAN_DEPLOY_TIMEOUT) {
throw new RuntimeException("Web plan did not start within " + (WEB_PLAN_DEPLOY_TIMEOUT / 1000) + " seconds.");
}
}
}
protected PathReference hotDeploy(PathReference toDeploy, String name, String version) throws InterruptedException {
PathReference deployed = toDeploy.copy(new PathReference("build/pickup"), true);
awaitDeployment(toDeploy, deployed);
awaitWebAppStart(name, version);
return deployed;
}
private void awaitDeployment(PathReference toDeploy, PathReference deployed) throws InterruptedException {
long startTime = System.currentTimeMillis();
while (!(this.appDeployer.isDeployed(deployed.toURI()))) {
Thread.sleep(100);
if (System.currentTimeMillis() - startTime > HOT_DEPLOY_TIMEOUT) {
throw new RuntimeException(toDeploy + " failed to deploy within " + (HOT_DEPLOY_TIMEOUT / 1000) + " seconds.");
}
}
}
private void awaitWebAppStart(String name, String version) throws InterruptedException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
try {
ObjectName objectName = new ObjectName(String.format("org.eclipse.virgo.kernel:type=ArtifactModel,artifact-type=bundle,name=%s,version=%s,region=%s", name,
version, USER_REGION_NAME));
ManageableArtifact artifact = JMX.newMXBeanProxy(mBeanServer, objectName, ManageableArtifact.class);
long startTime = System.currentTimeMillis();
while (artifact.getProperties().get("org.eclipse.virgo.web.contextPath") == null) {
Thread.sleep(100);
if (System.currentTimeMillis() - startTime > HOT_DEPLOY_TIMEOUT) {
throw new RuntimeException(name + " " + version + " failed to set its context path within " + (HOT_DEPLOY_TIMEOUT / 1000)
+ " seconds.");
}
}
} catch (JMException e) {
throw new RuntimeException(e);
}
}
protected void hotUnDeploy(PathReference deployed) throws InterruptedException {
URI uri = deployed.toURI();
assertTrue(this.appDeployer.isDeployed(uri));
deployed.delete(true);
long startTime = System.currentTimeMillis();
while (this.appDeployer.isDeployed(uri)) {
Thread.sleep(100);
if (System.currentTimeMillis() - startTime > HOT_DEPLOY_TIMEOUT) {
throw new RuntimeException(deployed + " failed to undeploy within " + (HOT_DEPLOY_TIMEOUT / 1000) + " seconds.");
}
}
}
}