001: /*
002: * ========================================================================
003: *
004: * Copyright 2001-2004 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * ========================================================================
019: */
020: package org.apache.cactus.internal.server;
021:
022: import java.io.IOException;
023: import java.io.Writer;
024:
025: import java.lang.reflect.Constructor;
026:
027: import javax.servlet.ServletException;
028:
029: import junit.framework.Test;
030: import junit.framework.TestCase;
031:
032: import org.apache.cactus.internal.CactusTestCase;
033: import org.apache.cactus.internal.HttpServiceDefinition;
034: import org.apache.cactus.internal.ServiceEnumeration;
035: import org.apache.cactus.internal.WebTestResult;
036: import org.apache.cactus.internal.configuration.Version;
037: import org.apache.cactus.internal.util.ClassLoaderUtils;
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040:
041: /**
042: * Responsible for instanciating the <code>TestCase</code> class on the server
043: * side, set up the implicit objects and call the test method. This class
044: * provides a common abstraction for all test web requests.
045: *
046: * @version $Id: AbstractWebTestCaller.java 238991 2004-05-22 11:34:50Z vmassol $
047: */
048: public abstract class AbstractWebTestCaller {
049: /**
050: * Name of the attribute in the <code>application</code> scope that will
051: * hold the results of the test.
052: */
053: protected static final String TEST_RESULTS = "ServletTestRedirector_TestResults";
054:
055: /**
056: * The logger.
057: */
058: private static final Log LOGGER = LogFactory
059: .getLog(AbstractWebTestCaller.class);
060:
061: /**
062: * The implicit objects (which will be used to set the test case fields
063: * in the <code>setTesCaseFields</code> method.
064: */
065: protected WebImplicitObjects webImplicitObjects;
066:
067: /**
068: * @param theObjects the implicit objects coming from the redirector
069: */
070: public AbstractWebTestCaller(WebImplicitObjects theObjects) {
071: this .webImplicitObjects = theObjects;
072: }
073:
074: /**
075: * Sets the implicit object in the test case class
076: *
077: * @param theTestCase the instance of the test case class on which the
078: * class variable (implicit objects) should be set
079: * @exception Exception if an errors occurs when setting the implicit
080: * objects
081: */
082: protected abstract void setTestCaseFields(TestCase theTestCase)
083: throws Exception;
084:
085: /**
086: * @return a <code>Writer</code> object that will be used to return the
087: * test result to the client side.
088: * @exception IOException if an error occurs when retrieving the writer
089: */
090: protected abstract Writer getResponseWriter() throws IOException;
091:
092: /**
093: * Calls a test method. The parameters needed to call this method are found
094: * in the HTTP request. Save the results in the <code>application</code>
095: * scope so that the Get Test Result service can find them.
096: *
097: * @exception ServletException if an unexpected error occurred
098: */
099: public void doTest() throws ServletException {
100: WebTestResult result = null;
101:
102: try {
103: // Create an instance of the test class
104: TestCase testInstance = getTestClassInstance(
105: getTestClassName(), getWrappedTestClassName(),
106: getTestMethodName());
107:
108: // Set its fields (implicit objects)
109: setTestCaseFields(testInstance);
110:
111: // Call it's method corresponding to the current test case
112: if (testInstance instanceof CactusTestCase) {
113: ((CactusTestCase) testInstance).runBareServer();
114:
115: } else {
116: testInstance.runBare();
117: }
118:
119: // Return an instance of <code>WebTestResult</code> with a
120: // positive result.
121: result = new WebTestResult();
122: } catch (Throwable e) {
123: // An error occurred, return an instance of
124: // <code>WebTestResult</code> with an exception.
125: result = new WebTestResult(e);
126: }
127:
128: LOGGER.debug("Test result : [" + result + "]");
129:
130: // Set the test result.
131: this .webImplicitObjects.getServletContext().setAttribute(
132: TEST_RESULTS, result);
133:
134: LOGGER.debug("Result saved in context scope");
135: }
136:
137: /**
138: * Return the last test results in the HTTP response.
139: *
140: * @exception ServletException if an unexpected error occurred
141: */
142: public void doGetResults() throws ServletException {
143: // One could think there is a potential risk that the client side of
144: // Cactus will request the result before it has been written to the
145: // context scope as the HTTP request will not block in some containers.
146: // However this will not happen because on the client side, once the
147: // first request is done to execute the test, all the result is read
148: // by the AutoReadHttpURLConnection class, thus ensuring that the
149: // request is fully finished and the result has been committed ...
150: WebTestResult result = (WebTestResult) (this .webImplicitObjects
151: .getServletContext().getAttribute(TEST_RESULTS));
152:
153: // It can happen that the result has not been written in the Servlet
154: // context. This could happen for example when using a load-balancer
155: // which would direct the second Cactus HTTP connection to another
156: // instance. In that case, we throw an error.
157: if (result == null) {
158: String message = "Error getting test result. This could happen "
159: + "for example if you're using a load-balancer. Please disable "
160: + "it before running Cactus tests.";
161:
162: LOGGER.error(message);
163: throw new ServletException(message);
164: }
165:
166: LOGGER.debug("Test Result = [" + result + "]");
167:
168: // Write back the results to the outgoing stream as an XML string.
169:
170: // Use UTF-8 to transfer the result back
171: webImplicitObjects.getHttpServletResponse().setContentType(
172: "text/xml; charset=UTF-8");
173:
174: try {
175: Writer writer = getResponseWriter();
176:
177: writer.write(result.toXml());
178: writer.close();
179: } catch (IOException e) {
180: String message = "Error writing WebTestResult instance to output "
181: + "stream";
182:
183: LOGGER.error(message, e);
184: throw new ServletException(message, e);
185: }
186: }
187:
188: /**
189: * Run the connection test between client and server. This is just to
190: * ensure that configuration is set up correctly.
191: *
192: * @exception ServletException if an unexpected error occurred
193: */
194: public void doRunTest() throws ServletException {
195: // Do not return any http response (not needed). It is enough to
196: // know this point has been reached ... it means the connection has
197: // been established !
198: }
199:
200: /**
201: * Return the cactus version. This is to make sure both the client side
202: * and server side are using the same version.
203: *
204: * @exception ServletException if an unexpected error occurred
205: */
206: public void doGetVersion() throws ServletException {
207: try {
208: Writer writer = getResponseWriter();
209: writer.write(Version.VERSION);
210: writer.close();
211: } catch (IOException e) {
212: String message = "Error writing HTTP response back to client "
213: + "for service ["
214: + ServiceEnumeration.GET_VERSION_SERVICE + "]";
215:
216: LOGGER.error(message, e);
217: throw new ServletException(message, e);
218: }
219: }
220:
221: /**
222: * Create an HTTP Session and returns the response that contains the
223: * HTTP session as a cookie (unless URL rewriting is used in which
224: * case the jsesssionid cookie is not returned).
225: *
226: * @exception ServletException if an unexpected error occurred
227: */
228: public void doCreateSession() throws ServletException {
229: // Create an HTTP session
230: this .webImplicitObjects.getHttpServletRequest()
231: .getSession(true);
232:
233: try {
234: Writer writer = getResponseWriter();
235: writer.close();
236: } catch (IOException e) {
237: String message = "Error writing HTTP response back to client "
238: + "for service ["
239: + ServiceEnumeration.CREATE_SESSION_SERVICE + "]";
240:
241: LOGGER.error(message, e);
242: throw new ServletException(message, e);
243: }
244: }
245:
246: /**
247: * @return the class to test class name, extracted from the HTTP request
248: * @exception ServletException if the class name of the test case is missing
249: * from the HTTP request
250: */
251: protected String getTestClassName() throws ServletException {
252: String queryString = this .webImplicitObjects
253: .getHttpServletRequest().getQueryString();
254: String className = ServletUtil.getQueryStringParameter(
255: queryString, HttpServiceDefinition.CLASS_NAME_PARAM);
256:
257: if (className == null) {
258: String message = "Missing class name parameter ["
259: + HttpServiceDefinition.CLASS_NAME_PARAM
260: + "] in HTTP request.";
261:
262: LOGGER.error(message);
263: throw new ServletException(message);
264: }
265:
266: LOGGER.debug("Class to call = [" + className + "]");
267:
268: return className;
269: }
270:
271: /**
272: * @return the optional test class that is wrapped by a Cactus test case,
273: * extracted from the HTTP request
274: * @exception ServletException if the wrapped class name is missing from
275: * the HTTP request
276: */
277: protected String getWrappedTestClassName() throws ServletException {
278: String queryString = this .webImplicitObjects
279: .getHttpServletRequest().getQueryString();
280: String className = ServletUtil.getQueryStringParameter(
281: queryString,
282: HttpServiceDefinition.WRAPPED_CLASS_NAME_PARAM);
283:
284: if (className == null) {
285: LOGGER.debug("No wrapped test class");
286: } else {
287: LOGGER.debug("Wrapped test class = [" + className + "]");
288: }
289:
290: return className;
291: }
292:
293: /**
294: * @return the class method to call for the current test case, extracted
295: * from the HTTP request
296: * @exception ServletException if the method name of the test case is
297: * missing from the HTTP request
298: */
299: protected String getTestMethodName() throws ServletException {
300: String queryString = this .webImplicitObjects
301: .getHttpServletRequest().getQueryString();
302: String methodName = ServletUtil.getQueryStringParameter(
303: queryString, HttpServiceDefinition.METHOD_NAME_PARAM);
304:
305: if (methodName == null) {
306: String message = "Missing method name parameter ["
307: + HttpServiceDefinition.METHOD_NAME_PARAM
308: + "] in HTTP request.";
309:
310: LOGGER.error(message);
311: throw new ServletException(message);
312: }
313:
314: LOGGER.debug("Method to call = " + methodName);
315:
316: return methodName;
317: }
318:
319: /**
320: * @return true if the auto session flag for the Session can be found in
321: * the HTTP request
322: */
323: protected boolean isAutoSession() {
324: String queryString = this .webImplicitObjects
325: .getHttpServletRequest().getQueryString();
326: String autoSession = ServletUtil.getQueryStringParameter(
327: queryString,
328: HttpServiceDefinition.AUTOSESSION_NAME_PARAM);
329:
330: boolean isAutomaticSession = Boolean.valueOf(autoSession)
331: .booleanValue();
332:
333: LOGGER.debug("Auto session is " + isAutomaticSession);
334:
335: return isAutomaticSession;
336: }
337:
338: /**
339: * @param theClassName the name of the test class
340: * @param theWrappedClassName the name of the wrapped test class. Can be
341: * null if there is none
342: * @param theTestCaseName the name of the current test case
343: * @return an instance of the test class to call
344: * @exception ServletException if the test case instance for the current
345: * test fails to be instanciated (for example if some
346: * information is missing from the HTTP request)
347: */
348: protected TestCase getTestClassInstance(String theClassName,
349: String theWrappedClassName, String theTestCaseName)
350: throws ServletException {
351: // Get the class to call and build an instance of it.
352: Class testClass = getTestClassClass(theClassName);
353: TestCase testInstance = null;
354: Constructor constructor;
355:
356: try {
357: if (theWrappedClassName == null) {
358: constructor = getTestClassConstructor(testClass);
359:
360: if (constructor.getParameterTypes().length == 0) {
361: testInstance = (TestCase) constructor
362: .newInstance(new Object[0]);
363: ((TestCase) testInstance).setName(theTestCaseName);
364: } else {
365: testInstance = (TestCase) constructor
366: .newInstance(new Object[] { theTestCaseName });
367: }
368: } else {
369: Class wrappedTestClass = getTestClassClass(theWrappedClassName);
370: Constructor wrappedConstructor = getTestClassConstructor(wrappedTestClass);
371:
372: TestCase wrappedTestInstance;
373: if (wrappedConstructor.getParameterTypes().length == 0) {
374: wrappedTestInstance = (TestCase) wrappedConstructor
375: .newInstance(new Object[0]);
376: wrappedTestInstance.setName(theTestCaseName);
377: } else {
378: wrappedTestInstance = (TestCase) wrappedConstructor
379: .newInstance(new Object[] { theTestCaseName });
380: }
381:
382: constructor = testClass.getConstructor(new Class[] {
383: String.class, Test.class });
384:
385: testInstance = (TestCase) constructor
386: .newInstance(new Object[] { theTestCaseName,
387: wrappedTestInstance });
388: }
389: } catch (Exception e) {
390: String message = "Error instantiating class ["
391: + theClassName + "([" + theTestCaseName + "], ["
392: + theWrappedClassName + "])]";
393:
394: LOGGER.error(message, e);
395: throw new ServletException(message, e);
396: }
397:
398: return testInstance;
399: }
400:
401: /**
402: * @param theTestClass the test class for which we want to find the
403: * constructor
404: * @return the availble constructor for the test class
405: * @throws NoSuchMethodException if no suitable constructor is found
406: */
407: private Constructor getTestClassConstructor(Class theTestClass)
408: throws NoSuchMethodException {
409: Constructor constructor;
410: try {
411: constructor = theTestClass
412: .getConstructor(new Class[] { String.class });
413: } catch (NoSuchMethodException e) {
414: constructor = theTestClass.getConstructor(new Class[0]);
415: }
416: return constructor;
417: }
418:
419: /**
420: * @param theClassName the name of the test class
421: * @return the class object the test class to call
422: * @exception ServletException if the class of the current test case
423: * cannot be loaded in memory (i.e. it is not in the
424: * classpath)
425: */
426: protected Class getTestClassClass(String theClassName)
427: throws ServletException {
428: // Get the class to call and build an instance of it.
429: Class testClass = null;
430:
431: try {
432: testClass = ClassLoaderUtils.loadClass(theClassName, this
433: .getClass());
434: } catch (Exception e) {
435: String message = "Error finding class ["
436: + theClassName
437: + "] using both the Context classloader and the webapp "
438: + "classloader. Possible causes include:\r\n";
439:
440: message += ("\t- Your webapp does not include your test "
441: + "classes,\r\n");
442: message += ("\t- The cactus.jar is not located in your "
443: + "WEB-INF/lib directory and your Container has not set the "
444: + "Context classloader to point to the webapp one");
445:
446: LOGGER.error(message, e);
447: throw new ServletException(message, e);
448: }
449:
450: return testClass;
451: }
452:
453: }
|