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  
22  package org.apache.cactus.integration.ant;
23  
24  import java.io.File;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.util.Enumeration;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.ResourceBundle;
31  
32  import org.apache.cactus.container.ContainerRunner;
33  import org.apache.cactus.container.ContainerWrapper;
34  import org.apache.cactus.integration.ant.util.PropertySet;
35  import org.apache.cactus.integration.api.deployable.DeployableFile;
36  import org.apache.cactus.integration.api.deployable.EarParser;
37  import org.apache.cactus.integration.api.deployable.WarParser;
38  import org.apache.tools.ant.BuildException;
39  import org.apache.tools.ant.Project;
40  import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
41  import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
42  import org.apache.tools.ant.types.Environment;
43  import org.apache.tools.ant.types.Path;
44  import org.apache.tools.ant.types.Environment.Variable;
45  import org.codehaus.cargo.container.Container;
46  import org.codehaus.cargo.util.log.AntLogger;
47  
48  /**
49   * An Ant task that extends the optional JUnit task to provide support for
50   * in-container testing.
51   *
52   * @version $Id: CactusTask.java 239202 2005-08-11 18:48:07Z felipeal $
53   */
54  public class CactusTask extends JUnitTask
55  {
56      // Instance Variables ------------------------------------------------------
57      /**
58       * The nested containerset element.
59       */
60      private ContainerSet containerSet;
61      
62      
63  
64      /**
65       * The archive that contains the enterprise application that should be
66       * tested.
67       */
68      private File earFile;
69  
70      /**
71       * The archive that contains the web-app that is ready to be tested.
72       */
73      private File warFile;
74  
75      /**
76       * System properties that will be set in the container JVM.
77       */
78      private Map systemProperties = new HashMap();
79  
80      /**
81       * Additional classpath entries for the classpath that will be used to 
82       * start the containers.
83       */
84      private Path containerClasspath;
85      
86      // Constructors ------------------------------------------------------------
87      
88      /**
89       * Constructor.
90       * 
91       * @throws Exception If the constructor of JUnitTask throws an exception
92       */
93      public CactusTask() throws Exception
94      {
95          // TODO: Fix comment for this constructor as it doesn't seem quite 
96          // right. Explain why we don't call the super constructor?
97      }
98  
99      // Public Methods ----------------------------------------------------------
100 
101     /**
102      * @see org.apache.tools.ant.Task#init()
103      */
104     public void init()
105     {
106         super.init();
107         
108         addClasspathEntry("/org/aspectj/lang/JoinPoint.class");
109         addClasspathEntry("/org/apache/cactus/ServletTestCase.class");
110         addClasspathEntry(
111             "/org/apache/cactus/integration/ant/CactusTask.class");
112         addClasspathEntry("/org/apache/commons/logging/Log.class");
113         addClasspathEntry("/org/apache/commons/httpclient/HttpClient.class");
114     }
115 
116     /**
117      * {@inheritDoc}
118      * @see org.apache.tools.ant.Task#execute()
119      */
120     public void execute() throws BuildException
121     {
122         if ((this.warFile == null) && (this.earFile == null))
123         {
124             throw new BuildException("You must specify either the [warfile] or "
125                 + "the [earfile] attribute");
126         }
127 
128         if ((this.warFile != null) && (this.earFile != null))
129         {
130             throw new BuildException("You must specify either the [warfile] or "
131                 + "the [earfile] attribute but not both");
132         }
133 
134         // Parse deployment descriptors for WAR or EAR files
135         DeployableFile deployableFile;
136         if (this.warFile != null)
137         {
138             deployableFile = WarParser.parse(this.warFile);
139         }
140         else 
141         {
142             deployableFile = EarParser.parse(this.earFile);
143         } 
144 
145         addRedirectorNameProperties(deployableFile);
146         
147         if (containerSet == null || containerSet.getCargos() == null)
148         {
149             log("No cargo configurations specified, tests will run locally",
150                 Project.MSG_VERBOSE);
151             super.execute();
152         }
153         else
154         {
155             CargoElement[] cargoElements = this.containerSet.getCargos();
156             Variable contextUrl = new Variable();
157             contextUrl.setKey("cactus.contextURL");
158 
159             addSysproperty(contextUrl);
160 
161             for (int i = 0; i < cargoElements.length; i++)
162             {
163                 CargoElement element = (CargoElement) cargoElements[i];
164                 Container container = element.getCargoContainer();
165                 ContainerWrapper wrapper = new ContainerWrapper(container);
166                 wrapper.setLogger(new AntLogger(this));
167 
168                 // Clone the DeployableFile instance as each container can
169                 // override default deployment properties (e.g. port, context
170                 // root, etc).
171                 DeployableFile thisDeployable = null;
172                 try
173                 {
174                     thisDeployable = (DeployableFile) deployableFile.clone();
175                 }
176                 catch (CloneNotSupportedException e)
177                 {
178                     throw new BuildException(e);
179                 }
180 
181                 // Allow the container to override the default test context. 
182                 // This is to support container extensions to the web.xml file.
183                 // Most containers allow defining the root context in these
184                 // extensions.
185                 wrapper.setSystemProperties(this.systemProperties);
186 
187                 // Add extra classpath entries
188                 if (containerClasspath != null)
189                 {
190                     wrapper.setContainerClasspath(this.containerClasspath
191                         .list());
192                 }
193                 
194                 if (wrapper.isEnabled())
195                 {
196                     wrapper.init();
197                     log("--------------------------------------------------"
198                         + "---------------", Project.MSG_INFO);
199                     log("Running tests against " + wrapper.getName()
200                         + " @ " + wrapper.getBaseURL(),
201                         Project.MSG_INFO);
202                     log("--------------------------------------------------"
203                         + "---------------", Project.MSG_INFO);
204                     contextUrl.setValue(wrapper.getBaseURL() + "/"
205                         + thisDeployable.getTestContext());
206                     executeInContainer(wrapper, thisDeployable); 
207                 }
208             }
209         }
210     }
211 
212     /**
213      * Adds the nested containers element (only one is permitted).
214      * 
215      * @param theContainerSet The nested element to add
216      */
217     public final void addContainerSet(ContainerSet theContainerSet)
218     {
219         if (this.containerSet != null)
220         {
221             throw new BuildException(
222                 "Only one nested containerset element supported");
223         }
224         this.containerSet = theContainerSet;
225     }
226 
227     /**
228      * Sets the enterprise application archive that will be tested. It must
229      * already contain the test-cases and the required libraries as a web
230      * module.
231      * 
232      * @param theEarFile The EAR file to set  
233      */
234     public final void setEarFile(File theEarFile)
235     {
236         if (this.warFile != null)
237         {
238             throw new BuildException(
239                 "You may only specify one of [earfile] and [warfile]");
240         }
241         this.earFile = theEarFile;
242     }
243 
244     /**
245      * Sets the web application archive that will be tested. It must already 
246      * contain the test-cases and the required libraries.
247      * 
248      * @param theWarFile The WAR file to set  
249      */
250     public final void setWarFile(File theWarFile)
251     {
252         if (this.earFile != null)
253         {
254             throw new BuildException(
255                 "You may only specify one of [earfile] and [warfile]");
256         }
257         this.warFile = theWarFile;
258     }
259 
260     /**
261      * Adds a system property to both client side and server side JVMs.
262      *  
263      * {@inheritDoc}
264      * @see JUnitTask#addSysproperty(Environment.Variable) 
265      */
266     public void addSysproperty(Environment.Variable theProperty)
267     {
268         addCactusServerProperty(theProperty);
269         if (theProperty.getKey() != null 
270             && !theProperty.getKey().trim().equals("") 
271             && theProperty.getValue() != null 
272             && !theProperty.getValue().trim().equals(""))
273         {
274             addCactusClientProperty(theProperty.getKey(), 
275                 theProperty.getValue());
276         }
277         super.addSysproperty(theProperty);
278     }
279 
280     /**
281      * Called by Ant when the Variable object has been properly initialized.
282      * 
283      * @param theProperty the system property to set 
284      */
285     public void addConfiguredSysproperty(Environment.Variable theProperty)
286     {
287         addSysproperty(theProperty);
288     }
289 
290     /**
291      * Adds a set of properties that will be used as system properties
292      * either on the client side or on the server side.
293      *
294      * @param thePropertySet the set of properties to be added
295      */
296     public void addConfiguredCactusproperty(PropertySet thePropertySet)
297     {
298         // Add all properties from the properties file
299         ResourceBundle bundle = thePropertySet.readProperties();
300         Enumeration keys = bundle.getKeys();
301         while (keys.hasMoreElements())
302         {
303             String key = (String) keys.nextElement();
304             Variable var = new Variable();
305             var.setKey(key);
306             var.setValue(bundle.getString(key));
307             if (thePropertySet.isServer())
308             {
309                 addCactusServerProperty(var);
310             }
311             else
312             {
313                 super.addSysproperty(var);
314             }
315         }
316     }
317 
318     /**
319      * Adds container classpath to the classpath that will be used for starting
320      * the container. 
321      *
322      * @return reference to the classpath
323      * @since Cactus 1.6
324      */
325     public Path createContainerClasspath()
326     {
327         if (this.containerClasspath == null)
328         {
329             this.containerClasspath = new Path(this.project);            
330         }
331         
332         return this.containerClasspath.createPath();
333     }    
334     
335     // Private Methods ---------------------------------------------------------
336 
337     /**
338      * Adds a Cactus system property for the client side JVM.
339      * 
340      * @param theKey The property name
341      * @param theValue The property value
342      */
343     private void addCactusClientProperty(String theKey, String theValue)
344     {
345         log("Adding Cactus client system property [" + theKey 
346             + "] with value [" + theValue + "]", Project.MSG_VERBOSE);
347         Variable sysProperty = new Variable();
348         sysProperty.setKey(theKey);
349         sysProperty.setValue(theValue);
350         super.addSysproperty(sysProperty);
351     }
352 
353     /**
354      * Adds a Cactus system property for the server side JVM.
355      * 
356      * @param theProperty The system property to set in the container JVM
357      */
358     private void addCactusServerProperty(Variable theProperty)
359     {
360         //TODO We always need to check this below, because null properties
361         // break the cargo execution
362         if (theProperty.getKey() == null 
363             || theProperty.getKey().trim().equals("")
364             || theProperty.getValue() == null 
365             || theProperty.getValue().trim().equals("")) 
366         {
367             return;
368         }
369     
370         log("Adding Cactus server system property [" 
371             + theProperty.getKey() + "] with value [" 
372             + theProperty.getValue() + "]", Project.MSG_VERBOSE);
373         this.systemProperties.put(theProperty.getKey(), theProperty.getValue());
374     }
375 
376     /**
377      * Adds a Cactus system property for the server side JVM.
378      * 
379      * @param theKey The property name
380      * @param theValue The property value
381      */
382     private void addCactusServerProperty(String theKey, String theValue)
383     {
384         Variable property = new Variable();
385         property.setKey(theKey);
386         property.setValue(theValue);
387         addCactusServerProperty(property);
388     }
389     
390     /**
391      * Extracts the redirector mappings from the deployment descriptor and sets 
392      * the corresponding system properties.
393      * 
394      * @param theFile The file to deploy in the container
395      */
396     private void addRedirectorNameProperties(DeployableFile theFile)
397     {
398         String filterRedirectorMapping = 
399             theFile.getFilterRedirectorMapping();
400         if (filterRedirectorMapping != null)
401         {
402             addCactusClientProperty("cactus.filterRedirectorName",
403                 filterRedirectorMapping.substring(1));
404         }
405         else
406         {
407             log("No mapping of the filter redirector found",
408                 Project.MSG_VERBOSE);
409         }
410 
411         String jspRedirectorMapping = 
412             theFile.getJspRedirectorMapping();
413         if (jspRedirectorMapping != null)
414         {
415             addCactusClientProperty("cactus.jspRedirectorName",
416                 jspRedirectorMapping.substring(1));
417         }
418         else
419         {
420             log("No mapping of the JSP redirector found",
421                 Project.MSG_VERBOSE);
422         }
423 
424         String servletRedirectorMapping = 
425             theFile.getServletRedirectorMapping();
426         
427         if (servletRedirectorMapping != null)
428         {
429             addCactusClientProperty("cactus.servletRedirectorName",
430                 servletRedirectorMapping.substring(1));
431         }
432         else
433         {
434             throw new BuildException("The WAR has not been cactified");
435         }
436     }
437 
438     /**
439      * Executes the unit tests in the given container.
440      * 
441      * @param theWrapper The containerWrapper to run the tests against
442      * @param theFile the file to deploy in the container
443      */
444     private void executeInContainer(ContainerWrapper theWrapper, 
445         DeployableFile theFile)
446     {
447         super.init();
448         log("Starting up container", Project.MSG_VERBOSE);
449         ContainerRunner runner = new ContainerRunner(theWrapper);
450         runner.setLogger(new AntLogger(getProject()));
451         try
452         {
453             URL url = new URL(theWrapper.getBaseURL() + "/"
454                 + theFile.getTestContext() 
455                 + theFile.getServletRedirectorMapping()
456                 + "?Cactus_Service=RUN_TEST");
457             runner.setURL(url);
458          
459             runner.startUpContainer();
460             log("Server name retrieved from 'Server' HTTP header: ["
461                 + runner.getServerName() + "]", Project.MSG_VERBOSE);
462             try
463             {
464                 Enumeration tests = getIndividualTests();
465                 while (tests.hasMoreElements())
466                 {
467                     JUnitTest test = (JUnitTest) tests.nextElement();
468                     if (test.shouldRun(getProject())
469                      && !theWrapper.isExcluded(test.getName()))
470                     {
471                         test.setFork(true);
472                         if (theWrapper.getToDir() != null)
473                         {
474                             test.setTodir(theWrapper.getToDir());
475                         }
476                         execute(test);
477                     }
478                 }
479             }
480             finally
481             {
482                 log("Shutting down container", Project.MSG_VERBOSE);
483                 runner.shutDownContainer();
484                 log("Container shut down", Project.MSG_VERBOSE);
485             }
486         }
487         catch (MalformedURLException mue)
488         {
489             throw new BuildException("Malformed test URL", mue);
490         }
491     }
492 }