| /******************************************************************************* |
| * 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."); |
| } |
| } |
| } |
| |
| } |