2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

View Javadoc

1   /* 
2    * ========================================================================
3    * 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   * 
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * 
19   * ========================================================================
20   */
21  package org.apache.cactus.internal.server;
22  
23  import java.io.IOException;
24  import java.io.Writer;
25  
26  import java.lang.reflect.Constructor;
27  
28  import javax.servlet.ServletException;
29  
30  import junit.framework.Test;
31  import junit.framework.TestCase;
32  
33  import org.apache.cactus.internal.CactusTestCase;
34  import org.apache.cactus.internal.HttpServiceDefinition;
35  import org.apache.cactus.internal.ServiceEnumeration;
36  import org.apache.cactus.internal.WebTestResult;
37  import org.apache.cactus.internal.configuration.Version;
38  import org.apache.cactus.internal.util.ClassLoaderUtils;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * Responsible for instanciating the <code>TestCase</code> class on the server
44   * side, set up the implicit objects and call the test method. This class
45   * provides a common abstraction for all test web requests.
46   *
47   * @version $Id: AbstractWebTestCaller.java 238991 2004-05-22 11:34:50Z vmassol $
48   */
49  public abstract class AbstractWebTestCaller
50  {
51      /**
52       * Name of the attribute in the <code>application</code> scope that will
53       * hold the results of the test.
54       */
55      protected static final String TEST_RESULTS = 
56          "ServletTestRedirector_TestResults";
57  
58      /**
59       * The logger.
60       */
61      private static final Log LOGGER = 
62          LogFactory.getLog(AbstractWebTestCaller.class);
63  
64      /**
65       * The implicit objects (which will be used to set the test case fields
66       * in the <code>setTesCaseFields</code> method.
67       */
68      protected WebImplicitObjects webImplicitObjects;
69  
70      /**
71       * @param theObjects the implicit objects coming from the redirector
72       */
73      public AbstractWebTestCaller(WebImplicitObjects theObjects)
74      {
75          this.webImplicitObjects = theObjects;
76      }
77  
78      /**
79       * Sets the implicit object in the test case class.
80       *
81       * @param theTestCase the instance of the test case class on which the
82       *        class variable (implicit objects) should be set
83       * @exception Exception if an errors occurs when setting the implicit
84       *            objects
85       */
86      protected abstract void setTestCaseFields(TestCase theTestCase) 
87          throws Exception;
88  
89      /**
90       * @return a <code>Writer</code> object that will be used to return the
91       *         test result to the client side.
92       * @exception IOException if an error occurs when retrieving the writer
93       */
94      protected abstract Writer getResponseWriter() throws IOException;
95  
96      /**
97       * Calls a test method. The parameters needed to call this method are found
98       * in the HTTP request. Save the results in the <code>application</code>
99       * scope so that the Get Test Result service can find them.
100      *
101      * @exception ServletException if an unexpected error occurred
102      */
103     public void doTest() throws ServletException
104     {
105         WebTestResult result = null;
106 
107         try
108         {
109             // Create an instance of the test class
110             TestCase testInstance = getTestClassInstance(
111                 getTestClassName(), getWrappedTestClassName(), 
112                 getTestMethodName());
113 
114             // Set its fields (implicit objects)
115             setTestCaseFields(testInstance);
116 
117             // Call it's method corresponding to the current test case
118             if (testInstance instanceof CactusTestCase)
119             {
120                 ((CactusTestCase) testInstance).runBareServer();                
121                 
122             }
123             else
124             {
125                 testInstance.runBare();                
126             }
127 
128             // Return an instance of <code>WebTestResult</code> with a
129             // positive result.
130             result = new WebTestResult();
131         }
132         catch (Throwable e)
133         {
134             // An error occurred, return an instance of
135             // <code>WebTestResult</code> with an exception.
136             result = new WebTestResult(e);
137         }
138 
139         LOGGER.debug("Test result : [" + result + "]");
140 
141 
142         // Set the test result.
143         this.webImplicitObjects.getServletContext()
144             .setAttribute(TEST_RESULTS, result);
145 
146         LOGGER.debug("Result saved in context scope");
147     }
148 
149     /**
150      * Return the last test results in the HTTP response.
151      *
152      * @exception ServletException if an unexpected error occurred
153      */
154     public void doGetResults() throws ServletException
155     {
156         // One could think there is a potential risk that the client side of
157         // Cactus will request the result before it has been written to the
158         // context scope as the HTTP request will not block in some containers.
159         // However this will not happen because on the client side, once the
160         // first request is done to execute the test, all the result is read
161         // by the AutoReadHttpURLConnection class, thus ensuring that the
162         // request is fully finished and the result has been committed ...
163         WebTestResult result = (WebTestResult) (this.webImplicitObjects
164             .getServletContext().getAttribute(TEST_RESULTS));
165 
166         // It can happen that the result has not been written in the Servlet
167         // context. This could happen for example when using a load-balancer
168         // which would direct the second Cactus HTTP connection to another
169         // instance. In that case, we throw an error.
170         if (result == null)
171         {
172             String message = "Error getting test result. This could happen "
173                 + "for example if you're using a load-balancer. Please disable "
174                 + "it before running Cactus tests.";
175 
176             LOGGER.error(message);
177             throw new ServletException(message);
178         }       
179 
180         LOGGER.debug("Test Result = [" + result + "]");
181 
182         // Write back the results to the outgoing stream as an XML string.
183 
184         // Use UTF-8 to transfer the result back
185         webImplicitObjects.getHttpServletResponse().setContentType(
186             "text/xml; charset=UTF-8");
187 
188         try
189         {
190             Writer writer = getResponseWriter();
191 
192             writer.write(result.toXml());
193             writer.close();
194         }
195         catch (IOException e)
196         {
197             String message = "Error writing WebTestResult instance to output "
198                 + "stream";
199 
200             LOGGER.error(message, e);
201             throw new ServletException(message, e);
202         }
203     }
204 
205     /**
206      * Run the connection test between client and server. This is just to
207      * ensure that configuration is set up correctly.
208      *
209      * @exception ServletException if an unexpected error occurred
210      */
211     public void doRunTest() throws ServletException
212     {
213         // Do not return any http response (not needed). It is enough to
214         // know this point has been reached ... it means the connection has
215         // been established !
216     }
217 
218     /**
219      * Return the cactus version. This is to make sure both the client side
220      * and server side are using the same version.
221      *  
222      * @exception ServletException if an unexpected error occurred
223      */    
224     public void doGetVersion() throws ServletException
225     {
226         try
227         {
228             Writer writer = getResponseWriter();
229             writer.write(Version.VERSION);
230             writer.close();
231         }
232         catch (IOException e)
233         {
234             String message = "Error writing HTTP response back to client "
235                 + "for service [" + ServiceEnumeration.GET_VERSION_SERVICE
236                 + "]";
237 
238             LOGGER.error(message, e);
239             throw new ServletException(message, e);
240         }        
241     }
242     
243     /**
244      * Create an HTTP Session and returns the response that contains the
245      * HTTP session as a cookie (unless URL rewriting is used in which
246      * case the jsesssionid cookie is not returned).
247      * 
248      * @exception ServletException if an unexpected error occurred
249      */
250     public void doCreateSession() throws ServletException
251     {
252         // Create an HTTP session
253         this.webImplicitObjects.getHttpServletRequest().getSession(true);
254 
255         try
256         {
257             Writer writer = getResponseWriter();
258             writer.close();
259         }
260         catch (IOException e)
261         {
262             String message = "Error writing HTTP response back to client "
263                 + "for service [" + ServiceEnumeration.CREATE_SESSION_SERVICE
264                 + "]";
265 
266             LOGGER.error(message, e);
267             throw new ServletException(message, e);
268         }
269     }
270 
271     /**
272      * @return the class to test class name, extracted from the HTTP request
273      * @exception ServletException if the class name of the test case is missing
274      *            from the HTTP request
275      */
276     protected String getTestClassName() throws ServletException
277     {
278         String queryString = this.webImplicitObjects.getHttpServletRequest()
279             .getQueryString();
280         String className = ServletUtil.getQueryStringParameter(queryString, 
281             HttpServiceDefinition.CLASS_NAME_PARAM);
282 
283         if (className == null)
284         {
285             String message = "Missing class name parameter ["
286                 + HttpServiceDefinition.CLASS_NAME_PARAM
287                 + "] in HTTP request.";
288 
289             LOGGER.error(message);
290             throw new ServletException(message);
291         }
292 
293         LOGGER.debug("Class to call = [" + className + "]");
294 
295         return className;
296     }
297 
298     /**
299      * @return the optional test class that is wrapped by a Cactus test case, 
300      *         extracted from the HTTP request
301      * @exception ServletException if the wrapped class name is missing from 
302      *            the HTTP request
303      */
304     protected String getWrappedTestClassName() throws ServletException
305     {
306         String queryString = this.webImplicitObjects.getHttpServletRequest()
307             .getQueryString();
308         String className = ServletUtil.getQueryStringParameter(queryString, 
309             HttpServiceDefinition.WRAPPED_CLASS_NAME_PARAM);
310 
311         if (className == null)
312         {
313             LOGGER.debug("No wrapped test class");
314         } 
315         else
316         { 
317             LOGGER.debug("Wrapped test class = [" + className + "]");
318         }
319 
320         return className;
321     }
322 
323     /**
324      * @return the class method to call for the current test case, extracted
325      *         from the HTTP request
326      * @exception ServletException if the method name of the test case is
327      *            missing from the HTTP request
328      */
329     protected String getTestMethodName() throws ServletException
330     {
331         String queryString = this.webImplicitObjects.getHttpServletRequest()
332             .getQueryString();
333         String methodName = ServletUtil.getQueryStringParameter(queryString, 
334             HttpServiceDefinition.METHOD_NAME_PARAM);
335 
336         if (methodName == null)
337         {
338             String message = "Missing method name parameter ["
339                 + HttpServiceDefinition.METHOD_NAME_PARAM
340                 + "] in HTTP request.";
341 
342             LOGGER.error(message);
343             throw new ServletException(message);
344         }
345 
346         LOGGER.debug("Method to call = " + methodName);
347 
348         return methodName;
349     }
350 
351     /**
352      * @return true if the auto session flag for the Session can be found in
353      *         the HTTP request
354      */
355     protected boolean isAutoSession()
356     {
357         String queryString = this.webImplicitObjects.getHttpServletRequest()
358             .getQueryString();
359         String autoSession = ServletUtil.getQueryStringParameter(queryString, 
360             HttpServiceDefinition.AUTOSESSION_NAME_PARAM);
361 
362         boolean isAutomaticSession = 
363             Boolean.valueOf(autoSession).booleanValue();
364 
365         LOGGER.debug("Auto session is " + isAutomaticSession);
366 
367         return isAutomaticSession;
368     }
369 
370     /**
371      * @param theClassName the name of the test class
372      * @param theWrappedClassName the name of the wrapped test class. Can be
373      *        null if there is none
374      * @param theTestCaseName the name of the current test case
375      * @return an instance of the test class to call
376      * @exception ServletException if the test case instance for the current
377      *            test fails to be instanciated (for example if some
378      *            information is missing from the HTTP request)
379      */
380     protected TestCase getTestClassInstance(
381         String theClassName, String theWrappedClassName, 
382         String theTestCaseName) throws ServletException
383     {
384         // Get the class to call and build an instance of it.
385         Class testClass = getTestClassClass(theClassName);
386         TestCase testInstance = null;
387         Constructor constructor;
388         
389         try
390         {
391             if (theWrappedClassName == null)
392             {
393                 constructor = getTestClassConstructor(testClass); 
394 
395                 if (constructor.getParameterTypes().length == 0)
396                 {
397                     testInstance = (TestCase) constructor.newInstance(
398                         new Object[0]);
399                     ((TestCase) testInstance).setName(theTestCaseName);
400                 }
401                 else
402                 {
403                     testInstance = (TestCase) constructor.newInstance(
404                         new Object[] {theTestCaseName});                
405                 }
406             }
407             else
408             {
409                 Class wrappedTestClass = 
410                     getTestClassClass(theWrappedClassName);
411                 Constructor wrappedConstructor =
412                     getTestClassConstructor(wrappedTestClass);
413 
414                 TestCase wrappedTestInstance;
415                 if (wrappedConstructor.getParameterTypes().length == 0)
416                 {
417                     wrappedTestInstance = 
418                         (TestCase) wrappedConstructor.newInstance(
419                         new Object[0]);
420                     wrappedTestInstance.setName(theTestCaseName);
421                 }
422                 else
423                 {
424                     wrappedTestInstance = 
425                         (TestCase) wrappedConstructor.newInstance(
426                         new Object[] {theTestCaseName});
427                 }
428 
429                 constructor = testClass.getConstructor(
430                     new Class[] {String.class, Test.class});
431 
432                 testInstance = 
433                     (TestCase) constructor.newInstance(
434                     new Object[] {theTestCaseName, wrappedTestInstance});
435             }
436         }
437         catch (Exception e)
438         {
439             String message = "Error instantiating class [" + theClassName + "(["
440                 + theTestCaseName + "], [" + theWrappedClassName + "])]";
441 
442             LOGGER.error(message, e);
443             throw new ServletException(message, e);
444         }
445 
446         return testInstance;
447     }
448 
449     /**
450      * @param theTestClass the test class for which we want to find the
451      *        constructor
452      * @return the availble constructor for the test class
453      * @throws NoSuchMethodException if no suitable constructor is found
454      */
455     private Constructor getTestClassConstructor(Class theTestClass)
456         throws NoSuchMethodException
457     {
458         Constructor constructor;
459         try 
460         {
461             constructor = theTestClass.getConstructor(
462                 new Class[] {String.class});         
463         }
464         catch (NoSuchMethodException e)
465         {
466             constructor = theTestClass.getConstructor(new Class[0]);
467         }
468         return constructor;        
469     }
470 
471     /**
472      * @param theClassName the name of the test class
473      * @return the class object the test class to call
474      * @exception ServletException if the class of the current test case
475      *            cannot be loaded in memory (i.e. it is not in the
476      *            classpath)
477      */
478     protected Class getTestClassClass(String theClassName)
479         throws ServletException
480     {
481         // Get the class to call and build an instance of it.
482         Class testClass = null;
483 
484         try
485         {
486             testClass = ClassLoaderUtils.loadClass(theClassName, 
487                 this.getClass());
488         }
489         catch (Exception e)
490         {
491             String message = "Error finding class [" + theClassName
492                 + "] using both the Context classloader and the webapp "
493                 + "classloader. Possible causes include:\r\n";
494 
495             message += ("\t- Your webapp does not include your test " 
496                 + "classes,\r\n");
497             message += ("\t- The cactus.jar is not located in your " 
498                 + "WEB-INF/lib directory and your Container has not set the " 
499                 + "Context classloader to point to the webapp one");
500 
501             LOGGER.error(message, e);
502             throw new ServletException(message, e);
503         }
504 
505         return testClass;
506     }
507 
508 }