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.client;
22  
23  import java.lang.reflect.InvocationTargetException;
24  import java.lang.reflect.Method;
25  
26  import junit.framework.Assert;
27  import junit.framework.Test;
28  
29  import org.apache.cactus.Request;
30  import org.apache.cactus.internal.util.JUnitVersionHelper;
31  import org.apache.cactus.internal.util.TestCaseImplementChecker;
32  import org.apache.cactus.spi.client.ResponseObjectFactory;
33  import org.apache.cactus.spi.client.connector.ProtocolHandler;
34  import org.apache.cactus.spi.client.connector.ProtocolState;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  /**
39   * Provides the ability to run common code before and after each test on the 
40   * client side. All the methods provided are independent of any communication 
41   * protocol between client side and server side (HTTP, JMS, etc). Any protocol 
42   * dependent methods must be provided and implemented in the 
43   * {@link ProtocolHandler} implementation class.
44   *  
45   * @version $Id: ClientTestCaseCaller.java 238991 2004-05-22 11:34:50Z vmassol $
46   */
47  public class ClientTestCaseCaller extends Assert
48  {
49      /**
50       * The prefix of a test method.
51       */
52      protected static final String TEST_METHOD_PREFIX = "test";
53  
54      /**
55       * The prefix of a begin test method.
56       */
57      protected static final String BEGIN_METHOD_PREFIX = "begin";
58  
59      /**
60       * The prefix of an end test method.
61       */
62      protected static final String END_METHOD_PREFIX = "end";
63  
64      /**
65       * The name of the method that is called before each test on the client
66       * side (if it exists).
67       */
68      protected static final String CLIENT_GLOBAL_BEGIN_METHOD = "begin";
69  
70      /**
71       * The name of the method that is called after each test on the client
72       * side (if it exists).
73       */
74      protected static final String CLIENT_GLOBAL_END_METHOD = "end";
75  
76      /**
77       * The logger (only used on the client side).
78       */
79      private Log logger;
80  
81      /**
82       * Pure JUnit Test Case that we are wrapping (if any).
83       */
84      private Test wrappedTest;
85  
86      /**
87       * The test we are delegating for.
88       */
89      private Test delegatedTest;   
90  
91      /**
92       * The protocol handler to use to execute the tests on the server side.
93       */
94      private ProtocolHandler protocolHandler;
95  
96      // Constructors ---------------------------------------------------------
97      
98      /**
99       * @param theDelegatedTest the test we are delegating for
100      * @param theWrappedTest the test being wrapped by this delegate (or null 
101      *        if none)
102      * @param theProtocolHandler the protocol handler to use to execute the 
103      *        tests on the server side 
104      */
105     public ClientTestCaseCaller(Test theDelegatedTest, 
106         Test theWrappedTest, ProtocolHandler theProtocolHandler)
107     {        
108         if (theDelegatedTest == null)
109         {
110             throw new IllegalStateException(
111                 "The test object passed must not be null");
112         }
113 
114         setDelegatedTest(theDelegatedTest); 
115         setWrappedTest(theWrappedTest);
116         this.protocolHandler = theProtocolHandler;
117     }
118 
119     // Public methods -------------------------------------------------------
120     
121     /**
122      * Execute begin and end methods and calls the different 
123      * {@link ProtocolHandler} lifecycle methods to execute the test
124      * on the server side.
125      * 
126      * Note that this method is overriden from the JUnit 
127      * {@link junit.framework.TestCase} class in order to prevent JUnit from 
128      * calling the {@link junit.framework.TestCase#setUp()} and
129      * {@link junit.framework.TestCase#tearDown()} methods on the client side.
130      * instead we are calling the server redirector proxy and the setup and
131      * teardown methods will be executed on the server side.
132      *
133      * @exception Throwable if any error happens during the execution of
134      *            the test
135      */
136     public void runTest() throws Throwable
137     {
138         Request request = this.protocolHandler.createRequest();
139         
140         // Call the set up and begin methods to fill the request object
141         callGlobalBeginMethod(request);
142         callBeginMethod(request);
143 
144         // Run the server test
145         ProtocolState state = this.protocolHandler.runTest(
146             getDelegatedTest(), getWrappedTest(), request);
147         
148         // Call the end method
149         Object response = callEndMethod(request, 
150             this.protocolHandler.createResponseObjectFactory(state));
151 
152         // call the tear down method
153         callGlobalEndMethod(request, 
154             this.protocolHandler.createResponseObjectFactory(state), 
155             response);
156 
157         this.protocolHandler.afterTest(state);
158     }
159 
160     /**
161      * @return The logger used by the <code>TestCase</code> class and
162      *         subclasses to perform logging.
163      */
164     public final Log getLogger()
165     {
166         return this.logger;
167     }
168 
169     /**
170      * Perform client side initializations before each test, such as
171      * re-initializating the logger and printing some logging information.
172      */
173     public void runBareInit()
174     {
175         // We make sure we reinitialize The logger with the name of the
176         // current extending class so that log statements will contain the
177         // actual class name (that's why the logged instance is not static).
178         this.logger = LogFactory.getLog(this.getClass());
179 
180         // Mark beginning of test on client side
181         getLogger().debug("------------- Test: " + this.getCurrentTestName());
182     }
183 
184     /**
185      * Call the test case begin method.
186      *
187      * @param theRequest the request object to pass to the begin method.
188      * @exception Throwable any error that occurred when calling the begin
189      *            method for the current test case.
190      */
191     public void callBeginMethod(Request theRequest) throws Throwable
192     {
193         callGenericBeginMethod(theRequest, getBeginMethodName());
194     }
195 
196     /**
197      * Call the test case end method.
198      * 
199      * @param theRequest the request data that were used to open the
200      *                   connection.
201      * @param theResponseFactory the factory to use to return response objects.
202      * @return the created Reponse object
203      * @exception Throwable any error that occurred when calling the end method
204      *         for the current test case.
205      */
206     public Object callEndMethod(Request theRequest, 
207         ResponseObjectFactory theResponseFactory) throws Throwable
208     {
209         return callGenericEndMethod(theRequest, theResponseFactory,
210             getEndMethodName(), null);
211     }
212 
213     /**
214      * Call the global begin method. This is the method that is called before
215      * each test if it exists. It is called on the client side only.
216      *
217      * @param theRequest the request object which will contain data that will
218      *        be used to connect to the Cactus server side redirectors.
219      * @exception Throwable any error that occurred when calling the method
220      */
221     public void callGlobalBeginMethod(Request theRequest) throws Throwable
222     {
223         callGenericBeginMethod(theRequest, CLIENT_GLOBAL_BEGIN_METHOD);
224     }
225 
226     /**
227      * Call the client tear down up method if it exists.
228      *
229      * @param theRequest the request data that were used to open the
230      *                   connection.
231      * @param theResponseFactory the factory to use to return response objects.
232      * @param theResponse the Response object if it exists. Can be null in
233      *        which case it is created from the response object factory
234      * @exception Throwable any error that occurred when calling the method
235      */
236     private void callGlobalEndMethod(Request theRequest, 
237         ResponseObjectFactory theResponseFactory, Object theResponse) 
238         throws Throwable
239     {
240         callGenericEndMethod(theRequest, theResponseFactory,
241             CLIENT_GLOBAL_END_METHOD, theResponse);
242     }
243     
244     // Private methods ------------------------------------------------------
245     
246     /**
247      * @param theWrappedTest the pure JUnit test that we need to wrap 
248      */
249     private void setWrappedTest(Test theWrappedTest)
250     {
251         this.wrappedTest = theWrappedTest;
252     }
253 
254     /**
255      * @param theDelegatedTest the test we are delegating for
256      */
257     private void setDelegatedTest(Test theDelegatedTest)
258     {
259         this.delegatedTest = theDelegatedTest;
260     }
261 
262     /**
263      * @return the wrapped JUnit test
264      */
265     private Test getWrappedTest()
266     {
267         return this.wrappedTest;
268     }
269 
270     /**
271      * @return the test we are delegating for
272      */
273     private Test getDelegatedTest()
274     {
275         return this.delegatedTest;
276     }
277 
278     /**
279      * @return the test on which we will operate. If there is a wrapped
280      *         test then the returned test is the wrapped test. Otherwise we
281      *         return the delegated test.
282      */
283     private Test getTest()
284     {
285         Test activeTest;
286         if (getWrappedTest() != null)
287         {
288             activeTest = getWrappedTest();
289         }
290         else
291         {
292             activeTest = getDelegatedTest();
293         }
294         return activeTest;
295     }
296 
297     /**
298      * @return the name of the test method to call without the
299      *         TEST_METHOD_PREFIX prefix
300      */
301     private String getBaseMethodName()
302     {
303         // Sanity check
304         if (!getCurrentTestName().startsWith(TEST_METHOD_PREFIX))
305         {
306             throw new RuntimeException("bad name ["
307                 + getCurrentTestName()
308                 + "]. It should start with ["
309                 + TEST_METHOD_PREFIX + "].");
310         }
311 
312         return getCurrentTestName().substring(
313             TEST_METHOD_PREFIX.length());
314     }
315 
316     /**
317      * @return the name of the test begin method to call that initialize the
318      *         test by initializing the <code>WebRequest</code> object
319      *         for the test case.
320      */
321     private String getBeginMethodName()
322     {
323         return BEGIN_METHOD_PREFIX + getBaseMethodName();
324     }
325 
326     /**
327      * @return the name of the test end method to call when the test has been
328      *         run on the server. It can be used to verify returned headers,
329      *         cookies, ...
330      */
331     private String getEndMethodName()
332     {
333         return END_METHOD_PREFIX + getBaseMethodName();
334     }
335 
336     /**
337      * Call a begin method which takes Cactus WebRequest as parameter.
338      *
339      * @param theRequest the request object which will contain data that will
340      *        be used to connect to the Cactus server side redirectors.
341      * @param theMethodName the name of the begin method to call
342      * @exception Throwable any error that occurred when calling the method
343      */
344     private void callGenericBeginMethod(Request theRequest, 
345         String theMethodName) throws Throwable
346     {
347         // First, verify if a begin method exist. If one is found, verify if
348         // it has the correct signature. If not, send a warning.
349         Method[] methods = getTest().getClass().getMethods();
350 
351         for (int i = 0; i < methods.length; i++)
352         {
353             if (methods[i].getName().equals(theMethodName))
354             {
355                 TestCaseImplementChecker.checkAsBeginMethod(methods[i]);
356 
357                 try
358                 {
359                     methods[i].invoke(getTest(), new Object[] {theRequest});
360 
361                     break;
362                 }
363                 catch (InvocationTargetException e)
364                 {
365                     e.fillInStackTrace();
366                     throw e.getTargetException();
367                 }
368                 catch (IllegalAccessException e)
369                 {
370                     e.fillInStackTrace();
371                     throw e;
372                 }
373             }
374         }
375     }
376 
377     /**
378      * Call the global end method. This is the method that is called after
379      * each test if it exists. It is called on the client side only.
380      *
381      * @param theRequest the request data that were used to open the
382      *        connection.
383      * @param theResponseFactory the factory to use to return response objects.
384      * @param theMethodName the name of the end method to call
385      * @param theResponse the Response object if it exists. Can be null in
386      *        which case it is created from the response object factory
387      * @return the created Reponse object
388      * @exception Throwable any error that occurred when calling the end method
389      *            for the current test case.
390      */
391     private Object callGenericEndMethod(Request theRequest,
392         ResponseObjectFactory theResponseFactory, String theMethodName, 
393         Object theResponse) throws Throwable
394     {
395         Method methodToCall = null;
396         Object paramObject = null;
397 
398         Method[] methods = getTest().getClass().getMethods();
399 
400         for (int i = 0; i < methods.length; i++)
401         {
402             if (methods[i].getName().equals(theMethodName))
403             {
404                 TestCaseImplementChecker.checkAsEndMethod(methods[i]);
405 
406                 paramObject = theResponse;
407 
408                 if (paramObject == null)
409                 {
410                     Class[] parameters = methods[i].getParameterTypes();
411                     try
412                     {
413                         paramObject = theResponseFactory.getResponseObject(
414                             parameters[0].getName(), theRequest);
415                     }
416                     catch (ClientException e)
417                     {
418                         throw new ClientException("The method ["
419                             + methods[i].getName() 
420                             + "] has a bad parameter of type ["
421                             + parameters[0].getName() + "]", e);
422                     }
423                 }
424 
425                 // Has a method to call already been found ?
426                 if (methodToCall != null)
427                 {
428                     fail("There can only be one method ["
429                        + methods[i].getName() + "] per test case. "
430                        + "Test case [" + this.getCurrentTestName()
431                        + "] has two at least !");
432                 }
433 
434                 methodToCall = methods[i];
435             }
436         }
437 
438         if (methodToCall != null)
439         {
440             try
441             {
442                 methodToCall.invoke(getTest(), new Object[] {paramObject});
443             }
444             catch (InvocationTargetException e)
445             {
446                 e.fillInStackTrace();
447                 throw e.getTargetException();
448             }
449             catch (IllegalAccessException e)
450             {
451                 e.fillInStackTrace();
452                 throw e;
453             }
454         }
455 
456         return paramObject;
457     }
458     
459     /**
460      * @return the name of the current test case being executed (it corresponds
461      *         to the name of the test method with the "test" prefix removed.
462      *         For example, for "testSomeTestOk" would return "someTestOk".
463      */
464     private String getCurrentTestName()
465     {
466         return JUnitVersionHelper.getTestCaseName(getDelegatedTest());        
467     }
468 }