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.integration.ant;
22  
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.text.DecimalFormat;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Random;
31  
32  import org.apache.cactus.integration.api.cactify.FilterRedirector;
33  import org.apache.cactus.integration.api.cactify.JspRedirector;
34  import org.apache.cactus.integration.api.cactify.Redirector;
35  import org.apache.cactus.integration.api.cactify.ServletRedirector;
36  import org.apache.cactus.integration.api.version.Version;
37  import org.apache.tools.ant.BuildException;
38  import org.apache.tools.ant.Project;
39  import org.apache.tools.ant.taskdefs.War;
40  import org.apache.tools.ant.types.FileSet;
41  import org.apache.tools.ant.types.XMLCatalog;
42  import org.apache.tools.ant.types.ZipFileSet;
43  import org.codehaus.cargo.container.internal.util.ResourceUtils;
44  import org.codehaus.cargo.module.webapp.DefaultWarArchive;
45  import org.codehaus.cargo.module.webapp.EjbRef;
46  import org.codehaus.cargo.module.webapp.WarArchive;
47  import org.codehaus.cargo.module.webapp.WebXml;
48  import org.codehaus.cargo.module.webapp.WebXmlIo;
49  import org.codehaus.cargo.module.webapp.WebXmlUtils;
50  import org.codehaus.cargo.module.webapp.WebXmlVersion;
51  import org.codehaus.cargo.module.webapp.merge.WebXmlMerger;
52  import org.codehaus.cargo.util.log.AntLogger;
53  import org.jdom.JDOMException;
54  
55  /**
56   * An Ant task that injects elements necessary to run Cactus tests into an
57   * existing WAR file.
58   *
59   * @version $Id: CactifyWarTask.java 394252 2006-04-15 04:20:17Z felipeal $
60   */
61  public class CactifyWarTask extends War
62  {
63  
64      // Constants ---------------------------------------------------------------
65  
66      /**
67       * Context of the cactus web application.
68       */
69      private String context;
70  
71      /**
72       * Name of the generated web app file.
73       */
74      private String FILE_NAME = "cactus.war";
75  
76      /**
77       * Get some non-crypto-grade randomness from various places.
78       */
79      private static Random rand = new Random(System.currentTimeMillis()
80              + Runtime.getRuntime().freeMemory());
81  
82      /**
83       * The name of the Cactus filter redirector class.
84       */
85      private static final String FILTER_REDIRECTOR_CLASS =
86          "org.apache.cactus.server.FilterTestRedirector";
87  
88      /**
89       * The default mapping of the Cactus filter redirector.
90       */
91      private static final String DEFAULT_FILTER_REDIRECTOR_MAPPING =
92          "/FilterRedirector";
93  
94      /**
95       * The default mapping of the Cactus JSP redirector.
96       */
97      private static final String DEFAULT_JSP_REDIRECTOR_MAPPING =
98          "/JspRedirector";
99  
100     /**
101      * The name of the Cactus servlet redirector class.
102      */
103     private static final String SERVLET_REDIRECTOR_CLASS =
104         "org.apache.cactus.server.ServletTestRedirector";
105 
106     /**
107      * The default mapping of the Cactus servlet redirector.
108      */
109     private static final String DEFAULT_SERVLET_REDIRECTOR_MAPPING =
110         "/ServletRedirector";
111 
112     // Inner Classes -----------------------------------------------------------
113 
114     // Instance Variables ------------------------------------------------------
115 
116     /**
117      * The archive that contains the web-app that should be cactified.
118      */
119     private File srcFile;
120 
121     /**
122      * Location of the descriptor of which the content should be merged into
123      * the descriptor of the cactified archive.
124      */
125     private File mergeWebXml;
126 
127     /**
128      * The Cactus test redirectors.
129      */
130     private List redirectors = new ArrayList();
131 
132     /**
133      * For resolving entities such as DTDs.
134      */
135     private XMLCatalog xmlCatalog = null;
136 
137     /**
138      * The web-app version to use when creating a WAR from scratch.
139      */
140     private String version = null;
141 
142     /**
143      * List of ejb-refs to add to the deployment descriptor
144      * of the cactified war.
145      */
146     private List ejbRefs = new ArrayList();
147     /**
148      * The cargo ResourceUtils.
149      */
150     private ResourceUtils utils = new ResourceUtils();
151 
152     /**
153      * Determines if cactus jars should be included.
154      */
155     private boolean includeCactusJars = true;
156 
157     // Public Methods ----------------------------------------------------------
158 
159 
160     /**
161      * {@inheritDoc}
162      * @see org.apache.tools.ant.Task#execute()
163      */
164     public void execute() throws BuildException
165     {
166         WebXml webXml = null;
167         if (this.srcFile != null)
168         {
169             log("Analyzing war: " + this.srcFile.getAbsolutePath(),
170                 Project.MSG_INFO);
171 
172             // Add everything that's in the source WAR to the destination WAR
173             ZipFileSet currentFiles = new ZipFileSet();
174             currentFiles.setSrc(this.srcFile);
175             currentFiles.createExclude().setName("WEB-INF/web.xml");
176             currentFiles.createExclude().setName("WEB-INF/weblogic.xml");
177             currentFiles.createExclude().setName("WEB-INF/orion-web.xml");
178             currentFiles.createExclude().setName("WEB-INF/ibm-web-bnd.xmi");
179             addZipfileset(currentFiles);
180 
181             // Parse the original deployment descriptor
182             try
183             {
184                 webXml = getOriginalWebXml();
185 
186             }
187             catch (JDOMException e)
188             {
189                 throw new BuildException("Unable to get the original exception", e);
190             }
191         }
192         if (this.srcFile == null || webXml == null)
193         {
194             if (this.version == null)
195             {
196                 throw new BuildException("You need to specify either the "
197                     + "[srcfile] or the [version] attribute");
198             }
199             WebXmlVersion webXmlVersion = null;
200             if (this.version.equals("2.2"))
201             {
202                 webXmlVersion = WebXmlVersion.V2_2;
203             }
204             else if (this.version.equals("2.3"))
205             {
206                 webXmlVersion = WebXmlVersion.V2_3;
207             }
208             else
209             {
210                 webXmlVersion = WebXmlVersion.V2_4;
211             }
212 
213             webXml = WebXmlIo.newWebXml(webXmlVersion);
214         }
215 
216         File tmpWebXml = null;
217         try
218         {
219             tmpWebXml = cactifyWebXml(webXml);
220         }
221         catch (JDOMException e)
222         {
223             throw new BuildException("Unable to cactify your application.", e);
224         }
225         setWebxml(tmpWebXml);
226 
227         if(includeCactusJars)
228         {
229             addCactusJars();
230         }
231 
232         try
233         {
234             super.execute();
235         }
236         finally
237         {
238             // Even though the temporary descriptor will get deleted
239             // automatically when the VM exits, delete it explicitly here just
240             // to be a better citizen
241             tmpWebXml.delete();
242         }
243     }
244 
245     /**
246      * Adds a Cactus filter test redirector.
247      *
248      * @param theFilterRedirector The redirector to add
249      */
250     public final void addFilterRedirector(FilterRedirector theFilterRedirector)
251     {
252         this.redirectors.add(theFilterRedirector);
253     }
254 
255     /**
256      * Adds a Cactus JSP test redirector.
257      *
258      * @param theJspRedirector The redirector to add
259      */
260     public final void addJspRedirector(JspRedirector theJspRedirector)
261     {
262         this.redirectors.add(theJspRedirector);
263     }
264 
265     /**
266      * Adds a Cactus servlet test redirector.
267      *
268      * @param theServletRedirector The redirector to add
269      */
270     public final void addServletRedirector(
271         ServletRedirector theServletRedirector)
272     {
273         this.redirectors.add(theServletRedirector);
274     }
275 
276     /**
277      * Adds an XML catalog to the internal catalog.
278      *
279      * @param theXmlCatalog the XMLCatalog instance to use to look up DTDs
280      */
281     public final void addConfiguredXMLCatalog(XMLCatalog theXmlCatalog)
282     {
283         if (this.xmlCatalog == null)
284         {
285             this.xmlCatalog = new XMLCatalog();
286             this.xmlCatalog.setProject(getProject());
287         }
288         this.xmlCatalog.addConfiguredXMLCatalog(theXmlCatalog);
289     }
290 
291     /**
292      * Adds a configured EjbRef instance. Called by Ant.
293      *
294      * @param theEjbRef the EjbRef to add
295      */
296     public final void addConfiguredEjbref(EjbRef theEjbRef)
297     {
298         ejbRefs.add(theEjbRef);
299     }
300 
301     /**
302      * The descriptor to merge into the original file.
303      *
304      * @param theMergeFile the <code>web.xml</code> to merge
305      */
306     public final void setMergeWebXml(File theMergeFile)
307     {
308         this.mergeWebXml = theMergeFile;
309     }
310 
311     /**
312      * Sets the web application archive that should be cactified.
313      *
314      * @param theSrcFile The WAR file to set
315      */
316     public final void setSrcFile(File theSrcFile)
317     {
318         this.srcFile = theSrcFile;
319     }
320 
321     /**
322      * Sets the web-app version to use when creating a WAR file from scratch.
323      *
324      * @param theVersion The version
325      */
326     public final void setVersion(Version theVersion)
327     {
328         this.version = theVersion.getValue();
329     }
330 
331     /**
332      * Sets whether the cactus jars should be included or not.
333      * If not set, the jars are included by default.
334      *
335      * @param includeCactusJars <code>true</code> if cactus jars should be included,
336      * <code>false</code> otherwise.
337      */
338     public final void setIncludeCactusJars(boolean includeCactusJars)
339     {
340         this.includeCactusJars = includeCactusJars;
341     }
342 
343 
344     // Private Methods ---------------------------------------------------------
345 
346     /**
347      * Adds the libraries required by Cactus on the server side.
348      */
349     private void addCactusJars()
350     {
351         addJarWithClass("org.aspectj.lang.JoinPoint", "AspectJ Runtime");
352         addJarWithClass("org.apache.cactus.ServletTestCase",
353             "Cactus Framework");
354         addJarWithClass("org.apache.commons.logging.Log",
355             "Commons-Logging");
356         addJarWithClass("org.apache.commons.httpclient.HttpClient",
357             "Commons-HttpClient");
358         addJarWithClass("junit.framework.TestCase", "JUnit");
359     }
360 
361     /**
362      * Adds the JAR file containing the specified resource to the WEB-INF/lib
363      * folder of a web-application archive.
364      *
365      * @param theClassName The name of the class that the JAR contains
366      * @param theDescription A description of the JAR that should be displayed
367      *        to the user in log messages
368      */
369     private void addJarWithClass(String theClassName, String theDescription)
370     {
371         String resourceName = "/" + theClassName.replace('.', '/') + ".class";
372         if (this.srcFile != null)
373         {
374             try
375             {
376                 WarArchive srcWar = new DefaultWarArchive(
377                     new FileInputStream(srcFile));
378                 if (srcWar.containsClass(theClassName))
379                 {
380                     log("The " + theDescription + " JAR is already present in "
381                         + "the WAR", Project.MSG_VERBOSE);
382                     return;
383                 }
384             }
385             catch (IOException ioe)
386             {
387                 log("Problem reading source WAR to when trying to detect "
388                     + "already present JAR files (" + ioe + ")",
389                     Project.MSG_WARN);
390             }
391         }
392         ZipFileSet jar = new ZipFileSet();
393 
394         File file = utils.getResourceLocation(resourceName);
395         if (file != null)
396         {
397             jar.setFile(file);
398             addLib(jar);
399         }
400         else
401         {
402             log("Could not find the " + theDescription + " JAR",
403                 Project.MSG_WARN);
404             log("You need to add the JAR to the classpath of the task",
405                 Project.MSG_INFO);
406             log("(Searched for class " + theClassName + ")", Project.MSG_DEBUG);
407         }
408     }
409 
410     /**
411      * Adds the Cactus JSP redirector file to the web application.
412      */
413     private void addJspRedirector()
414     {
415         // Now copy the actual JSP redirector file into the web application
416         File jspRedirectorFile = new File(
417             new File(System.getProperty("java.io.tmpdir")),
418             "jspRedirector.jsp");
419         jspRedirectorFile.deleteOnExit();
420         try
421         {
422             utils.copyResource("/org/apache/cactus/server/jspRedirector.jsp",
423                 jspRedirectorFile);
424         }
425         catch (IOException e)
426         {
427             log("Could not copy the JSP redirector (" + e.getMessage() + ")",
428                 Project.MSG_WARN);
429         }
430         FileSet fileSet = new FileSet();
431         fileSet.setFile(jspRedirectorFile);
432         addFileset(fileSet);
433     }
434 
435     /**
436      * Adds the definitions corresponding to the nested redirector elements to
437      * the provided deployment descriptor.
438      *
439      * @param theWebXml The deployment descriptor
440      */
441     private void addRedirectorDefinitions(WebXml theWebXml)
442     {
443         boolean filterRedirectorDefined = false;
444         boolean jspRedirectorDefined = false;
445         boolean servletRedirectorDefined = false;
446 
447         // add the user defined redirectors
448         for (Iterator i = this.redirectors.iterator(); i.hasNext();)
449         {
450 
451             Redirector redirector = (Redirector) i.next();
452             if (redirector instanceof FilterRedirector)
453             {
454                 filterRedirectorDefined = true;
455             }
456             else if (redirector instanceof JspRedirector)
457             {
458                 jspRedirectorDefined = true;
459             }
460             else if (redirector instanceof ServletRedirector)
461             {
462                 servletRedirectorDefined = true;
463             }
464             redirector.mergeInto(theWebXml);
465         }
466 
467         // now add the default redirectors if they haven't been provided by
468         // the user
469         if (!filterRedirectorDefined)
470         {
471             new FilterRedirector(new AntLogger(getProject()))
472                 .mergeInto(theWebXml);
473         }
474         if (!servletRedirectorDefined)
475         {
476             new ServletRedirector(new AntLogger(getProject()))
477                 .mergeInto(theWebXml);
478         }
479         if (!jspRedirectorDefined)
480         {
481             new JspRedirector(new AntLogger(getProject())).mergeInto(theWebXml);
482         }
483     }
484 
485     /**
486      * Enhances the provided web deployment descriptor with the definitions
487      * required for testing with Cactus.
488      *
489      * @param theWebXml The original deployment descriptor
490      * @return A temporary file containing the cactified descriptor
491      * @throws JDOMException in case a JDOM exception is thrown.
492      */
493     private File cactifyWebXml(WebXml theWebXml) throws JDOMException
494     {
495         addRedirectorDefinitions(theWebXml);
496         addJspRedirector();
497         addEjbRefs(theWebXml);
498 
499         // If the user has specified a deployment descriptor to merge into the
500         // cactified descriptor, perform the merge
501         if (this.mergeWebXml != null)
502         {
503             try
504             {
505                 WebXml parsedMergeWebXml = WebXmlIo.parseWebXmlFromFile(
506                     this.mergeWebXml, this.xmlCatalog);
507                 WebXmlMerger merger = new WebXmlMerger(theWebXml);
508                 merger.setLogger(new AntLogger(this));
509 
510                 merger.merge(parsedMergeWebXml);
511             }
512             catch (IOException e)
513             {
514                 throw new BuildException(
515                     "Could not merge deployment descriptors", e);
516             }
517         }
518 
519         // Serialize the cactified deployment descriptor into a temporary file,
520         // so that it can get picked up by the War task
521         //FileUtils fileUtils = FileUtils.newFileUtils();
522         File tmpDir = createTempFile("cactus", "tmp.dir",
523             getProject().getBaseDir(), true);
524         tmpDir.mkdirs();
525         tmpDir.deleteOnExit();
526         File webXmlFile = null;
527         try
528         {
529             ZipFileSet fileSet = new ZipFileSet();
530             fileSet.setDir(tmpDir);
531             tmpDir.mkdir();
532             File[] files = WebXmlIo.writeAll(theWebXml,
533                 tmpDir.getAbsolutePath());
534 
535 
536             for (int i = 0; i < files.length; i++)
537             {
538                 File f = files[i];
539                 f.deleteOnExit();
540                 if (f.getName().equals("web.xml"))
541                 {
542                     webXmlFile = f;
543                 }
544                 else
545                 {
546                     fileSet.createInclude().setName(f.getName());
547                 }
548             }
549             addWebinf(fileSet);
550         }
551         catch (IOException ioe)
552         {
553             throw new BuildException(
554                 "Could not write temporary deployment descriptor", ioe);
555         }
556         return webXmlFile;
557     }
558 
559     /**
560      * Extracts and parses the original web deployment descriptor from the
561      * web-app.
562      *
563      * @return The parsed descriptor or null if not found
564      * @throws BuildException If the descriptor could not be
565      *         parsed
566      * @throws JDOMException in case is JDOM exception is thrown.
567      */
568     private WebXml getOriginalWebXml() throws BuildException, JDOMException
569     {
570         // Open the archive as JAR file and extract the deployment descriptor
571         WarArchive war = null;
572         try
573         {
574             war = new DefaultWarArchive(new FileInputStream(this.srcFile));
575             WebXml webXml = war.getWebXml();
576             return webXml;
577         }
578         catch (IOException e)
579         {
580             throw new BuildException("Failed to open WAR", e);
581         }
582     }
583 
584     /**
585      * Add ejb references to a web.xml.
586      *
587      * @param theWebXml the web.xml to modify
588      */
589     private void addEjbRefs(WebXml theWebXml)
590     {
591         Iterator i = ejbRefs.iterator();
592         while (i.hasNext())
593         {
594             EjbRef ref = (EjbRef) i.next();
595             WebXmlUtils.addEjbRef(theWebXml, ref);
596         }
597     }
598 
599     /**
600      * A method to create the temporary files.
601      * @param thePrefix the prefix of the filename.
602      * @param theSuffix the suffix of the filename
603      * @param theParentDir the parent directory
604      * @param isDeleteOnExit should we delete the directories on exit?
605      * @return the temporary file
606      */
607     public File createTempFile(String thePrefix, String theSuffix,
608                                    File theParentDir, boolean isDeleteOnExit)
609     {
610     File result = null;
611     String parent = (theParentDir == null)
612             ? System.getProperty("java.io.tmpdir")
613             : theParentDir.getPath();
614 
615         DecimalFormat fmt = new DecimalFormat("#####");
616         synchronized (rand)
617         {
618             do
619             {
620                 result = new File(parent,
621                    thePrefix + fmt.format(Math.abs(rand.nextInt()))
622                    + theSuffix);
623             }
624             while (result.exists());
625         }
626         if (isDeleteOnExit)
627         {
628             result.deleteOnExit();
629         }
630         return result;
631     }
632 
633     /**
634      * Gets the file name.
635      *
636      * @return the name of the web app file
637      */
638     public String getFileName()
639     {
640         return FILE_NAME;
641     }
642 
643     /**
644      * Returns the context.
645      *
646      * @return <code>java.lang.String</code>
647      */
648     public String getContext() {
649         return context;
650     }
651 
652     /**
653      * Sets the context.
654      *
655      * @param context
656      */
657     public void setContext(String context) {
658         this.context = context;
659     }
660 
661 
662 }