Author: felipeal
Date: Sat Apr 15 17:26:20 2006
New Revision: 394406
URL: http://svn.apache.org/viewcvs?rev=394406&view=rev
Log:
CACTUS-151: this file was submitted on Xuan's patch, but I forgot to include it
earlier
Added:
jakarta/cactus/trunk/framework/src/java/share-13-14/org/apache/cactus/extension/jetty/Jetty4xTestSetup.java
Added:
jakarta/cactus/trunk/framework/src/java/share-13-14/org/apache/cactus/extension/jetty/Jetty4xTestSetup.java
URL:
http://svn.apache.org/viewcvs/jakarta/cactus/trunk/framework/src/java/share-13-14/org/apache/cactus/extension/jetty/Jetty4xTestSetup.java?rev=394406&view=auto
==============================================================================
---
jakarta/cactus/trunk/framework/src/java/share-13-14/org/apache/cactus/extension/jetty/Jetty4xTestSetup.java
(added)
+++
jakarta/cactus/trunk/framework/src/java/share-13-14/org/apache/cactus/extension/jetty/Jetty4xTestSetup.java
Sat Apr 15 17:26:20 2006
@@ -0,0 +1,586 @@
+/*
+ * ========================================================================
+ *
+ * Copyright 2001-2004 The Apache Software Foundation.
+ *
+ * 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.apache.cactus.extension.jetty;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import junit.extensions.TestSetup;
+import junit.framework.Protectable;
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.cactus.internal.configuration.BaseConfiguration;
+import org.apache.cactus.internal.configuration.Configuration;
+import org.apache.cactus.internal.configuration.DefaultFilterConfiguration;
+import org.apache.cactus.internal.configuration.DefaultServletConfiguration;
+import org.apache.cactus.internal.configuration.FilterConfiguration;
+import org.apache.cactus.internal.configuration.ServletConfiguration;
+import org.apache.cactus.internal.util.ClassLoaderUtils;
+import org.apache.cactus.server.FilterTestRedirector;
+import org.apache.cactus.server.ServletTestRedirector;
+
+
+/**
+ * Custom JUnit test setup to use to automatically start Jetty. Example:<br/>
+ * <code><pre>
+ * public static Test suite()
+ * {
+ * TestSuite suite = new TestSuite(Myclass.class);
+ * return new JettyTestSetup(suite);
+ * }
+ * </pre></code>
+ *
+ * @version $Id: JettyTestSetup.java,v 1.2 2004/08/17 10:35:57 vmassol Exp $
+ */
+public class Jetty4xTestSetup extends TestSetup
+{
+ /**
+ * Name of optional system property that points to a Jetty XML
+ * configuration file.
+ */
+ private static final String CACTUS_JETTY_CONFIG_PROPERTY =
+ "cactus.jetty.config";
+
+ /**
+ * Name of optional system property that gives the directory
+ * where JSPs and other resources are located.
+ */
+ private static final String CACTUS_JETTY_RESOURCE_DIR_PROPERTY =
+ "cactus.jetty.resourceDir";
+
+ /**
+ * The configuration file to be used for initializing Jetty.
+ */
+ private File configFile;
+
+ /**
+ * The directory containing the resources of the web-application.
+ */
+ private File resourceDir;
+
+ /**
+ * The Jetty server object representing the running instance. It is
+ * used to stop Jetty in [EMAIL PROTECTED] #tearDown()}.
+ */
+ private Object server;
+
+ /**
+ * Whether the container had already been running before.
+ */
+ private boolean alreadyRunning;
+
+ /**
+ * Whether the container is running or not.
+ */
+ private boolean isRunning = false;
+
+ /**
+ * Whether the container should be stopped on tearDown even though
+ * it was not started by us.
+ */
+ private boolean forceShutdown = false;
+
+ /**
+ * The Servlet configuration object used to configure Jetty.
+ */
+ private ServletConfiguration servletConfiguration;
+
+ /**
+ * The Filter configuration object used to configure Jetty.
+ */
+ private FilterConfiguration filterConfiguration;
+
+ /**
+ * The base configuration object used to configure Jetty.
+ */
+ private Configuration baseConfiguration;
+
+ /**
+ * @param theTest the test we are decorating (usually a test suite)
+ */
+ public Jetty4xTestSetup(Test theTest)
+ {
+ super(theTest);
+ this.baseConfiguration = new BaseConfiguration();
+ this.servletConfiguration = new DefaultServletConfiguration();
+ this.filterConfiguration = new DefaultFilterConfiguration();
+ }
+
+ /**
+ * @param theTest the test we are decorating (usually a test suite)
+ * @param theBaseConfiguration the base configuration object used to
+ * configure Jetty
+ * @param theServletConfiguration the servlet configuration object used
+ * to configure Jetty
+ * @param theFilterConfiguration the filter configuration object used
+ * to configure Jetty
+ */
+ public Jetty4xTestSetup(Test theTest,
+ Configuration theBaseConfiguration,
+ ServletConfiguration theServletConfiguration,
+ FilterConfiguration theFilterConfiguration)
+ {
+ this(theTest);
+ this.baseConfiguration = theBaseConfiguration;
+ this.servletConfiguration = theServletConfiguration;
+ this.filterConfiguration = theFilterConfiguration;
+ }
+
+ /**
+ * Make sure that [EMAIL PROTECTED] #tearDown} is called if [EMAIL
PROTECTED] #setUp} fails
+ * to start the container properly. The default
+ * [EMAIL PROTECTED] TestSetup#run(TestResult)} method does not provide
this feature
+ * unfortunately.
+ *
+ * @see TestSetup#run(TestResult)
+ */
+ public void run(final TestResult theResult)
+ {
+ Protectable p = new Protectable()
+ {
+ public void protect() throws Exception
+ {
+ try
+ {
+ setUp();
+ basicRun(theResult);
+ }
+ finally
+ {
+ tearDown();
+ }
+ }
+ };
+ theResult.runProtected(this, p);
+ }
+
+ /**
+ * Start an embedded Jetty server. It is allowed to pass a Jetty XML as
+ * a system property (<code>cactus.jetty.config</code>) to further
+ * configure Jetty. Example:
+ * <code>-Dcactus.jetty.config=./jetty.xml</code>.
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ protected void setUp() throws Exception
+ {
+ // Try connecting in case the server is already running. If so, does
+ // nothing
+ URL contextURL = new URL(this.baseConfiguration.getContextURL()
+ + "/" + this.servletConfiguration.getDefaultRedirectorName()
+ + "?Cactus_Service=RUN_TEST");
+ this.alreadyRunning = isAvailable(testConnectivity(contextURL));
+ if (this.alreadyRunning)
+ {
+ // Server is already running. Record this information so that we
+ // don't stop it afterwards.
+ this.isRunning = true;
+ return;
+ }
+
+ // Note: We are currently using reflection in order not to need Jetty
+ // to compile Cactus. If the code becomes more complex or we need to
+ // add other initializer, it will be worth considering moving them
+ // to a separate "extension" subproject which will need additional jars
+ // in its classpath (using the same mechanism as the Ant project is
+ // using to conditionally compile tasks).
+
+ // Create a Jetty Server object and configure a listener
+ this.server = createServer(this.baseConfiguration);
+
+ // Create a Jetty context.
+ Object context = createContext(this.server, this.baseConfiguration);
+
+ // Add the Cactus Servlet redirector
+ addServletRedirector(context, this.servletConfiguration);
+
+ // Add the Cactus Jsp redirector
+ addJspRedirector(context);
+
+ // Add the Cactus Filter redirector
+ addFilterRedirector(context, this.filterConfiguration);
+
+ // Configure Jetty with an XML file if one has been specified on the
+ // command line.
+ if (getConfigFile() != null)
+ {
+ this.server.getClass().getMethod("configure",
+ new Class[] {String.class}).invoke(
+ this.server, new Object[] {getConfigFile().toString()});
+ }
+
+ // Start the Jetty server
+ this.server.getClass().getMethod("start", null).invoke(
+ this.server, null);
+
+ this.isRunning = true;
+ }
+
+ /**
+ * Stop the running Jetty server.
+ *
+ * @exception Exception if an error happens during the shutdown
+ */
+ protected void tearDown() throws Exception
+ {
+ // Don't shut down a container that has not been started by us
+ if (!this.forceShutdown && this.alreadyRunning)
+ {
+ return;
+ }
+
+ if (this.server != null)
+ {
+ // First, verify if the server is running
+ boolean started = ((Boolean) this.server.getClass().getMethod(
+ "isStarted", null).invoke(this.server, null)).booleanValue();
+
+ // Stop and destroy the Jetty server, if started
+ if (started)
+ {
+ // Stop all listener and contexts
+ this.server.getClass().getMethod("stop", null).invoke(
+ this.server, null);
+
+ // Destroy a stopped server. Remove all components and send
+ // notifications to all event listeners.
+ this.server.getClass().getMethod("destroy", null).invoke(
+ this.server, null);
+ }
+ }
+
+ this.isRunning = false;
+ }
+
+ /**
+ * Sets the configuration file to use for initializing Jetty.
+ *
+ * @param theConfigFile The configuration file to set
+ */
+ public final void setConfigFile(File theConfigFile)
+ {
+ this.configFile = theConfigFile;
+ }
+
+ /**
+ * Sets the directory in which Jetty will look for the web-application
+ * resources.
+ *
+ * @param theResourceDir The resource directory to set
+ */
+ public final void setResourceDir(File theResourceDir)
+ {
+ this.resourceDir = theResourceDir;
+ }
+
+ /**
+ * @param isForcedShutdown if true the container will be stopped even
+ * if it has not been started by us
+ */
+ public final void setForceShutdown(boolean isForcedShutdown)
+ {
+ this.forceShutdown = isForcedShutdown;
+ }
+
+ /**
+ * @return The resource directory, or <code>null</code> if it has not been
+ * set
+ */
+ protected final File getConfigFile()
+ {
+ if (this.configFile == null)
+ {
+ String configFileProperty = System.getProperty(
+ CACTUS_JETTY_CONFIG_PROPERTY);
+ if (configFileProperty != null)
+ {
+ this.configFile = new File(configFileProperty);
+ }
+ }
+ return this.configFile;
+ }
+
+ /**
+ * @return The resource directory, or <code>null</code> if it has not been
+ * set
+ */
+ protected final File getResourceDir()
+ {
+ if (this.resourceDir == null)
+ {
+ String resourceDirProperty = System.getProperty(
+ CACTUS_JETTY_RESOURCE_DIR_PROPERTY);
+ if (resourceDirProperty != null)
+ {
+ this.resourceDir = new File(resourceDirProperty);
+ }
+ }
+ return this.resourceDir;
+ }
+
+ /**
+ * Create a Jetty server object and configures a listener on the
+ * port defined in the Cactus context URL property.
+ *
+ * @param theConfiguration the base Cactus configuration
+ * @return the Jetty <code>Server</code> object
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ private Object createServer(Configuration theConfiguration)
+ throws Exception
+ {
+ // Create Jetty Server object
+ Class serverClass = ClassLoaderUtils.loadClass(
+ "org.mortbay.jetty.Server", this.getClass());
+ Object server = serverClass.newInstance();
+
+ URL contextURL = new URL(theConfiguration.getContextURL());
+
+ // Add a listener on the port defined in the Cactus configuration
+ server.getClass().getMethod("addListener",
+ new Class[] {String.class})
+ .invoke(server, new Object[] {"" + contextURL.getPort()});
+
+ return server;
+ }
+
+ /**
+ * Create a Jetty Context. We use a <code>WebApplicationContext</code>
+ * because we need to use Servlet Filters.
+ *
+ * @param theServer the Jetty Server object
+ * @param theConfiguration the base Cactus configuration
+ * @return Object the <code>WebApplicationContext</code> object
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ private Object createContext(Object theServer,
+ Configuration theConfiguration) throws Exception
+ {
+ // Add a web application. This creates a WebApplicationContext.
+ // Note: We do not put any WEB-INF/, lib/ nor classes/ directory
+ // in the webapp.
+ URL contextURL = new URL(theConfiguration.getContextURL());
+
+ if (getResourceDir() != null)
+ {
+ theServer.getClass().getMethod("addWebApplication",
+ new Class[] {String.class, String.class})
+ .invoke(theServer, new Object[] {contextURL.getPath(),
+ getResourceDir().toString()});
+ }
+
+ // Retrieves the WebApplication context created by the
+ // "addWebApplication". We need it to be able to manually configure
+ // other items in the context.
+ Object context = theServer.getClass().getMethod(
+ "getContext", new Class[] {String.class})
+ .invoke(theServer, new Object[] {contextURL.getPath()});
+
+ return context;
+ }
+
+ /**
+ * Adds the Cactus Servlet redirector configuration
+ *
+ * @param theContext the Jetty context under which to add the configuration
+ * @param theConfiguration the Cactus Servlet configuration
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ private void addServletRedirector(Object theContext,
+ ServletConfiguration theConfiguration) throws Exception
+ {
+ theContext.getClass().getMethod("addServlet",
+ new Class[] {String.class, String.class, String.class})
+ .invoke(theContext,
+ new Object[] {theConfiguration.getDefaultRedirectorName(),
+ "/" + theConfiguration.getDefaultRedirectorName(),
+ ServletTestRedirector.class.getName()});
+ }
+
+ /**
+ * Adds the Cactus Jsp redirector configuration. We only add it if the
+ * CACTUS_JETTY_RESOURCE_DIR_PROPERTY has been provided by the user. This
+ * is because JSPs need to be attached to a WebApplicationHandler in Jetty.
+ *
+ * @param theContext the Jetty context under which to add the configuration
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ private void addJspRedirector(Object theContext) throws Exception
+ {
+ if (getResourceDir() != null)
+ {
+ theContext.getClass().getMethod("addServlet",
+ new Class[] {String.class, String.class})
+ .invoke(theContext,
+ new Object[] {"*.jsp",
+ "org.apache.jasper.servlet.JspServlet"});
+
+ // Get the WebApplicationHandler object in order to be able to
+ // call the addServlet() method that accpets a forced path.
+ Object handler = theContext.getClass().getMethod(
+ "getWebApplicationHandler",
+ new Class[] {}).invoke(theContext, new Object[] {});
+
+ handler.getClass().getMethod("addServlet",
+ new Class[] {String.class, String.class, String.class,
+ String.class})
+ .invoke(handler,
+ new Object[] {
+ "JspRedirector",
+ "/JspRedirector",
+ "org.apache.jasper.servlet.JspServlet",
+ "/jspRedirector.jsp"});
+ }
+ }
+
+ /**
+ * Adds the Cactus Filter redirector configuration. We only add it if the
+ * CACTUS_JETTY_RESOURCE_DIR_PROPERTY has been provided by the user. This
+ * is because Filters need to be attached to a WebApplicationHandler in
+ * Jetty.
+ *
+ * @param theContext the Jetty context under which to add the configuration
+ * @param theConfiguration the Cactus Filter configuration
+ *
+ * @exception Exception if an error happens during initialization
+ */
+ private void addFilterRedirector(Object theContext,
+ FilterConfiguration theConfiguration) throws Exception
+ {
+ if (getResourceDir() != null)
+ {
+ // Get the WebApplicationHandler object in order to be able to add
+ // the Cactus Filter redirector
+ Object handler = theContext.getClass().getMethod(
+ "getWebApplicationHandler",
+ new Class[] {}).invoke(theContext, new Object[] {});
+
+ Object filterHolder = handler.getClass().getMethod("defineFilter",
+ new Class[] {String.class, String.class})
+ .invoke(handler,
+ new Object[] {theConfiguration.getDefaultRedirectorName(),
+ FilterTestRedirector.class.getName()});
+
+ filterHolder.getClass().getMethod("addAppliesTo",
+ new Class[] {String.class})
+ .invoke(filterHolder, new Object[] {"REQUEST"});
+
+ // Map the Cactus Filter redirector to a path
+ handler.getClass().getMethod("mapPathToFilter",
+ new Class[] {String.class, String.class})
+ .invoke(handler,
+ new Object[] {"/"
+ + theConfiguration.getDefaultRedirectorName(),
+ theConfiguration.getDefaultRedirectorName()});
+ }
+ }
+
+ /**
+ * Tests whether we are able to connect to the HTTP server identified by
the
+ * specified URL.
+ *
+ * @param theUrl The URL to check
+ * @return the HTTP response code or -1 if no connection could be
+ * established
+ */
+ protected int testConnectivity(URL theUrl)
+ {
+ int code;
+ try
+ {
+ HttpURLConnection connection =
+ (HttpURLConnection) theUrl.openConnection();
+ connection.setRequestProperty("Connection", "close");
+ connection.connect();
+ readFully(connection);
+ connection.disconnect();
+ code = connection.getResponseCode();
+ }
+ catch (IOException e)
+ {
+ code = -1;
+ }
+ return code;
+ }
+
+ /**
+ * Tests whether an HTTP return code corresponds to a valid connection
+ * to the test URL or not. Success is 200 up to but excluding 300.
+ *
+ * @param theCode the HTTP response code to verify
+ * @return <code>true</code> if the test URL could be called without error,
+ * <code>false</code> otherwise
+ */
+ protected boolean isAvailable(int theCode)
+ {
+ boolean result;
+ if ((theCode != -1) && (theCode < 300))
+ {
+ result = true;
+ }
+ else
+ {
+ result = false;
+ }
+ return result;
+ }
+
+ /**
+ * Fully reads the input stream from the passed HTTP URL connection to
+ * prevent (harmless) server-side exception.
+ *
+ * @param theConnection the HTTP URL connection to read from
+ * @exception IOException if an error happens during the read
+ */
+ protected void readFully(HttpURLConnection theConnection)
+ throws IOException
+ {
+ // Only read if there is data to read ... The problem is that not
+ // all servers return a content-length header. If there is no header
+ // getContentLength() returns -1. It seems to work and it seems
+ // that all servers that return no content-length header also do
+ // not block on read() operations!
+ if (theConnection.getContentLength() != 0)
+ {
+ byte[] buf = new byte[256];
+ InputStream in = theConnection.getInputStream();
+ while (in.read(buf) != -1)
+ {
+ // Make sure we read all the data in the stream
+ }
+ }
+ }
+
+ /**
+ * @return true if the server is running or false otherwise
+ */
+ protected boolean isRunning()
+ {
+ return this.isRunning;
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]