001: /*
002: * ========================================================================
003: *
004: * Copyright 2003 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.integration.ant;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.StringTokenizer;
028:
029: import javax.xml.parsers.ParserConfigurationException;
030:
031: import org.apache.cactus.integration.ant.util.ResourceUtils;
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.Project;
034: import org.apache.tools.ant.taskdefs.War;
035: import org.apache.tools.ant.types.EnumeratedAttribute;
036: import org.apache.tools.ant.types.FileSet;
037: import org.apache.tools.ant.types.XMLCatalog;
038: import org.apache.tools.ant.types.ZipFileSet;
039: import org.apache.tools.ant.util.FileUtils;
040: import org.codehaus.cargo.module.webapp.DefaultWarArchive;
041: import org.codehaus.cargo.module.webapp.WarArchive;
042: import org.codehaus.cargo.module.webapp.WebXml;
043: import org.codehaus.cargo.module.webapp.WebXmlIo;
044: import org.codehaus.cargo.module.webapp.WebXmlMerger;
045: import org.codehaus.cargo.module.webapp.WebXmlVersion;
046: import org.codehaus.cargo.util.monitor.AntMonitor;
047: import org.xml.sax.SAXException;
048:
049: /**
050: * An Ant task that injects elements necessary to run Cactus tests into an
051: * existing WAR file.
052: *
053: * @version $Id: CactifyWarTask.java 239172 2005-05-17 09:14:26Z grimsell $
054: */
055: public class CactifyWarTask extends War {
056:
057: // Constants ---------------------------------------------------------------
058:
059: /**
060: * The name of the Cactus filter redirector class.
061: */
062: private static final String FILTER_REDIRECTOR_CLASS = "org.apache.cactus.server.FilterTestRedirector";
063:
064: /**
065: * The default mapping of the Cactus filter redirector.
066: */
067: private static final String DEFAULT_FILTER_REDIRECTOR_MAPPING = "/FilterRedirector";
068:
069: /**
070: * The default mapping of the Cactus JSP redirector.
071: */
072: private static final String DEFAULT_JSP_REDIRECTOR_MAPPING = "/JspRedirector";
073:
074: /**
075: * The name of the Cactus servlet redirector class.
076: */
077: private static final String SERVLET_REDIRECTOR_CLASS = "org.apache.cactus.server.ServletTestRedirector";
078:
079: /**
080: * The default mapping of the Cactus servlet redirector.
081: */
082: private static final String DEFAULT_SERVLET_REDIRECTOR_MAPPING = "/ServletRedirector";
083:
084: // Inner Classes -----------------------------------------------------------
085:
086: /**
087: * Abstract base class for nested redirector elements.
088: */
089: public abstract static class Redirector {
090:
091: // Instance Variables --------------------------------------------------
092:
093: /**
094: * The name of the redirector.
095: */
096: protected String name;
097:
098: /**
099: * The URL pattern that the redirector will be mapped to.
100: */
101: protected String mapping;
102:
103: /**
104: * Comma-separated list of role names that should be granted access to
105: * the redirector.
106: */
107: protected String roles;
108:
109: // Abstract Methods ----------------------------------------------------
110:
111: /**
112: * Merges the definition of the redirector into the provided deployment
113: * descriptor.
114: *
115: * @param theWebXml The deployment descriptor into which the redirector
116: * definition should be merged
117: */
118: public abstract void mergeInto(WebXml theWebXml);
119:
120: // Public Methods ------------------------------------------------------
121:
122: /**
123: * Sets the name of the redirector.
124: *
125: * @param theName The name to set
126: */
127: public final void setName(String theName) {
128: this .name = theName;
129: }
130:
131: /**
132: * Sets the URL pattern that the redirector should be mapped to.
133: *
134: * @param theMapping The URL pattern to set
135: */
136: public final void setMapping(String theMapping) {
137: this .mapping = theMapping;
138: }
139:
140: /**
141: * Sets the comma-separated list of role names that should be granted
142: * access to the redirector.
143: *
144: * @param theRoles The roles to set
145: */
146: public final void setRoles(String theRoles) {
147: this .roles = theRoles;
148: }
149:
150: // Protected Methods ---------------------------------------------------
151:
152: /**
153: * Adds the comma-separated list of security roles to a deployment
154: * descriptor.
155: *
156: * @param theWebXml The deployment descriptor
157: */
158: protected final void addSecurity(WebXml theWebXml) {
159: StringTokenizer tokenizer = new StringTokenizer(this .roles,
160: ",");
161: List roles = new ArrayList();
162: while (tokenizer.hasMoreTokens()) {
163: String role = tokenizer.nextToken().trim();
164: if (!theWebXml.hasSecurityRole(role)) {
165: theWebXml.addSecurityRole(role);
166: }
167: roles.add(role);
168: }
169: if (!roles.isEmpty()) {
170: if (!theWebXml.hasLoginConfig()) {
171: theWebXml.setLoginConfig("BASIC", "myrealm");
172: }
173: if (!theWebXml.hasSecurityConstraint(this .mapping)) {
174: theWebXml.addSecurityConstraint(
175: "Cactus Test Redirector", this .mapping,
176: roles);
177: }
178: }
179: }
180:
181: }
182:
183: /**
184: * Implementation of <code>Redirector</code> for filter test redirectors.
185: */
186: public static final class FilterRedirector extends Redirector {
187:
188: /**
189: * Default constructor.
190: */
191: public FilterRedirector() {
192: this .name = "FilterRedirector";
193: this .mapping = DEFAULT_FILTER_REDIRECTOR_MAPPING;
194: }
195:
196: /**
197: * @see CactifyWarTask.Redirector#mergeInto
198: */
199: public void mergeInto(WebXml theWebXml) {
200: if (WebXmlVersion.V2_3.compareTo(theWebXml.getVersion()) <= 0) {
201: theWebXml.addFilter(this .name, FILTER_REDIRECTOR_CLASS);
202: theWebXml.addFilterMapping(this .name, this .mapping);
203: if (this .roles != null) {
204: addSecurity(theWebXml);
205: }
206: }
207: }
208:
209: }
210:
211: /**
212: * Implementation of <code>Redirector</code> for JSP test redirectors.
213: */
214: public static final class JspRedirector extends Redirector {
215:
216: /**
217: * Default constructor.
218: */
219: public JspRedirector() {
220: this .name = "JspRedirector";
221: this .mapping = DEFAULT_JSP_REDIRECTOR_MAPPING;
222: }
223:
224: /**
225: * @see CactifyWarTask.Redirector#mergeInto
226: */
227: public void mergeInto(WebXml theWebXml) {
228: theWebXml.addJspFile(this .name, "/jspRedirector.jsp");
229: theWebXml.addServletMapping(this .name, this .mapping);
230: if (this .roles != null) {
231: addSecurity(theWebXml);
232: }
233: }
234:
235: }
236:
237: /**
238: * Implementation of <code>Redirector</code> for servlet test redirectors.
239: */
240: public static final class ServletRedirector extends Redirector {
241:
242: /**
243: * Default constructor.
244: */
245: public ServletRedirector() {
246: this .name = "ServletRedirector";
247: this .mapping = DEFAULT_SERVLET_REDIRECTOR_MAPPING;
248: }
249:
250: /**
251: * @see CactifyWarTask.Redirector#mergeInto
252: */
253: public void mergeInto(WebXml theWebXml) {
254: theWebXml.addServlet(this .name, SERVLET_REDIRECTOR_CLASS);
255: theWebXml.addServletMapping(this .name, this .mapping);
256: if (this .roles != null) {
257: addSecurity(theWebXml);
258: }
259: }
260:
261: }
262:
263: /**
264: * Enumeration for the <em>version</em> attribute.
265: */
266: public static final class Version extends EnumeratedAttribute {
267:
268: /**
269: * @see org.apache.tools.ant.types.EnumeratedAttribute#getValues()
270: */
271: public String[] getValues() {
272: return new String[] { "2.2", "2.3" };
273: }
274:
275: }
276:
277: /**
278: * Implements the nested element ejbref
279: */
280: public static final class EjbRef {
281: /**
282: * The name
283: */
284: private String name;
285: /**
286: * The local interface
287: */
288: private String localInterface;
289: /**
290: * The local home interface
291: */
292: private String localHomeInterface;
293: /**
294: * The jndi name
295: */
296: private String jndiName;
297: /**
298: * The type
299: */
300: private String type;
301:
302: /**
303: * Returns the jndi name
304: *
305: * @return Returns the jndiName.
306: */
307: public String getJndiName() {
308: return jndiName;
309: }
310:
311: /**
312: * Sets the jndiName
313: *
314: * @param theJndiName The jndiName to set.
315: */
316: public void setJndiName(String theJndiName) {
317: this .jndiName = theJndiName;
318: }
319:
320: /**
321: * Returns the local home interface
322: *
323: * @return Returns the localHomeInterface.
324: */
325: public String getLocalHomeInterface() {
326: return localHomeInterface;
327: }
328:
329: /**
330: * Sets the local home interface
331: *
332: * @param theLocalHomeInterface The localHomeInterface to set.
333: */
334: public void setLocalHomeInterface(String theLocalHomeInterface) {
335: this .localHomeInterface = theLocalHomeInterface;
336: }
337:
338: /**
339: * Return the local interface
340: *
341: * @return Returns the localInterface.
342: */
343: public String getLocalInterface() {
344: return localInterface;
345: }
346:
347: /**
348: * Sets the local interface
349: *
350: * @param theLocalInterface The localInterface to set.
351: */
352: public void setLocalInterface(String theLocalInterface) {
353: this .localInterface = theLocalInterface;
354: }
355:
356: /**
357: * Returns the name
358: *
359: * @return Returns the name.
360: */
361: public String getName() {
362: return name;
363: }
364:
365: /**
366: * Sets the name
367: *
368: * @param theName The name to set.
369: */
370: public void setName(String theName) {
371: this .name = theName;
372: }
373:
374: /**
375: * Returns the type
376: *
377: * @return Returns the type.
378: */
379: public String getType() {
380: return type;
381: }
382:
383: /**
384: * Sets the type
385: *
386: * @param theType The type to set.
387: */
388: public void setType(String theType) {
389: this .type = theType;
390: }
391: }
392:
393: // Instance Variables ------------------------------------------------------
394:
395: /**
396: * The archive that contains the web-app that should be cactified.
397: */
398: private File srcFile;
399:
400: /**
401: * Location of the descriptor of which the content should be merged into
402: * the descriptor of the cactified archive.
403: */
404: private File mergeWebXml;
405:
406: /**
407: * The Cactus test redirectors.
408: */
409: private List redirectors = new ArrayList();
410:
411: /**
412: * For resolving entities such as DTDs.
413: */
414: private XMLCatalog xmlCatalog = null;
415:
416: /**
417: * The web-app version to use when creating a WAR from scratch.
418: */
419: private String version = null;
420:
421: /**
422: * List of ejb-refs to add to the deployment descriptor of the cactified war
423: */
424: private List ejbRefs = new ArrayList();
425:
426: // Public Methods ----------------------------------------------------------
427:
428: /**
429: * @see org.apache.tools.ant.Task#execute()
430: */
431: public void execute() throws BuildException {
432: WebXml webXml = null;
433: if (this .srcFile != null) {
434: log("Analyzing war: " + this .srcFile.getAbsolutePath(),
435: Project.MSG_INFO);
436:
437: // Add everything that's in the source WAR to the destination WAR
438: ZipFileSet currentFiles = new ZipFileSet();
439: currentFiles.setSrc(this .srcFile);
440: currentFiles.createExclude().setName("WEB-INF/web.xml");
441: currentFiles.createExclude()
442: .setName("WEB-INF/weblogic.xml");
443: currentFiles.createExclude().setName(
444: "WEB-INF/orion-web.xml");
445: currentFiles.createExclude().setName(
446: "WEB-INF/ibm-web-bnd.xmi");
447: addZipfileset(currentFiles);
448:
449: // Parse the original deployment descriptor
450: webXml = getOriginalWebXml();
451: }
452: if (this .srcFile == null || webXml == null) {
453: if (this .version == null) {
454: throw new BuildException(
455: "You need to specify either the "
456: + "[srcfile] or the [version] attribute");
457: }
458: WebXmlVersion webXmlVersion = null;
459: if (this .version.equals("2.2")) {
460: webXmlVersion = WebXmlVersion.V2_2;
461: } else {
462: webXmlVersion = WebXmlVersion.V2_3;
463: }
464: try {
465: webXml = WebXmlIo.newWebXml(webXmlVersion);
466: } catch (ParserConfigurationException pce) {
467: throw new BuildException(
468: "Could not create deployment descriptor", pce);
469: }
470: }
471:
472: File tmpWebXml = cactifyWebXml(webXml);
473: setWebxml(tmpWebXml);
474:
475: addCactusJars();
476:
477: try {
478: super .execute();
479: } finally {
480: // Even though the temporary descriptor will get deleted
481: // automatically when the VM exits, delete it explicitly here just
482: // to be a better citizen
483: tmpWebXml.delete();
484: }
485: }
486:
487: /**
488: * Adds a Cactus filter test redirector.
489: *
490: * @param theFilterRedirector The redirector to add
491: */
492: public final void addFilterRedirector(
493: FilterRedirector theFilterRedirector) {
494: this .redirectors.add(theFilterRedirector);
495: }
496:
497: /**
498: * Adds a Cactus JSP test redirector.
499: *
500: * @param theJspRedirector The redirector to add
501: */
502: public final void addJspRedirector(JspRedirector theJspRedirector) {
503: this .redirectors.add(theJspRedirector);
504: }
505:
506: /**
507: * Adds a Cactus servlet test redirector.
508: *
509: * @param theServletRedirector The redirector to add
510: */
511: public final void addServletRedirector(
512: ServletRedirector theServletRedirector) {
513: this .redirectors.add(theServletRedirector);
514: }
515:
516: /**
517: * Adds an XML catalog to the internal catalog.
518: *
519: * @param theXmlCatalog the XMLCatalog instance to use to look up DTDs
520: */
521: public final void addConfiguredXMLCatalog(XMLCatalog theXmlCatalog) {
522: if (this .xmlCatalog == null) {
523: this .xmlCatalog = new XMLCatalog();
524: this .xmlCatalog.setProject(getProject());
525: }
526: this .xmlCatalog.addConfiguredXMLCatalog(theXmlCatalog);
527: }
528:
529: /**
530: * Adds a configured EjbRef instance. Called by Ant.
531: *
532: * @param theEjbRef the EjbRef to add
533: */
534: public final void addConfiguredEjbref(EjbRef theEjbRef) {
535: ejbRefs.add(theEjbRef);
536: }
537:
538: /**
539: * The descriptor to merge into the original file.
540: *
541: * @param theMergeFile the <code>web.xml</code> to merge
542: */
543: public final void setMergeWebXml(File theMergeFile) {
544: this .mergeWebXml = theMergeFile;
545: }
546:
547: /**
548: * Sets the web application archive that should be cactified.
549: *
550: * @param theSrcFile The WAR file to set
551: */
552: public final void setSrcFile(File theSrcFile) {
553: this .srcFile = theSrcFile;
554: }
555:
556: /**
557: * Sets the web-app version to use when creating a WAR file from scratch.
558: *
559: * @param theVersion The version
560: */
561: public final void setVersion(Version theVersion) {
562: this .version = theVersion.getValue();
563: }
564:
565: // Private Methods ---------------------------------------------------------
566:
567: /**
568: * Adds the libraries required by Cactus on the server side.
569: */
570: private void addCactusJars() {
571: addJarWithClass("org.aspectj.lang.JoinPoint", "AspectJ Runtime");
572: addJarWithClass("org.apache.cactus.ServletTestCase",
573: "Cactus Framework");
574: addJarWithClass("org.apache.commons.logging.Log",
575: "Commons-Logging");
576: addJarWithClass("org.apache.commons.httpclient.HttpClient",
577: "Commons-HttpClient");
578: addJarWithClass("junit.framework.TestCase", "JUnit");
579: }
580:
581: /**
582: * Adds the JAR file containing the specified resource to the WEB-INF/lib
583: * folder of a web-application archive.
584: *
585: * @param theClassName The name of the class that the JAR contains
586: * @param theDescription A description of the JAR that should be displayed
587: * to the user in log messages
588: */
589: private void addJarWithClass(String theClassName,
590: String theDescription) {
591: String resourceName = "/" + theClassName.replace('.', '/')
592: + ".class";
593: if (this .srcFile != null) {
594: try {
595: WarArchive srcWar = new DefaultWarArchive(srcFile);
596: if (srcWar.containsClass(theClassName)) {
597: log(
598: "The " + theDescription
599: + " JAR is already present in "
600: + "the WAR", Project.MSG_VERBOSE);
601: return;
602: }
603: } catch (IOException ioe) {
604: log(
605: "Problem reading source WAR to when trying to detect "
606: + "already present JAR files (" + ioe
607: + ")", Project.MSG_WARN);
608: }
609: }
610: ZipFileSet jar = new ZipFileSet();
611: File file = ResourceUtils.getResourceLocation(resourceName);
612: if (file != null) {
613: jar.setFile(file);
614: addLib(jar);
615: } else {
616: log("Could not find the " + theDescription + " JAR",
617: Project.MSG_WARN);
618: log("You need to add the JAR to the classpath of the task",
619: Project.MSG_INFO);
620: log("(Searched for class " + theClassName + ")",
621: Project.MSG_DEBUG);
622: }
623: }
624:
625: /**
626: * Adds the Cactus JSP redirector file to the web application.
627: */
628: private void addJspRedirector() {
629: // Now copy the actual JSP redirector file into the web application
630: File jspRedirectorFile = new File(new File(System
631: .getProperty("java.io.tmpdir")), "jspRedirector.jsp");
632: jspRedirectorFile.deleteOnExit();
633: try {
634: ResourceUtils.copyResource(getProject(),
635: "/org/apache/cactus/server/jspRedirector.jsp",
636: jspRedirectorFile);
637: } catch (IOException e) {
638: log("Could not copy the JSP redirector (" + e.getMessage()
639: + ")", Project.MSG_WARN);
640: }
641: FileSet fileSet = new FileSet();
642: fileSet.setFile(jspRedirectorFile);
643: addFileset(fileSet);
644: }
645:
646: /**
647: * Adds the definitions corresponding to the nested redirector elements to
648: * the provided deployment descriptor.
649: *
650: * @param theWebXml The deployment descriptor
651: */
652: private void addRedirectorDefinitions(WebXml theWebXml) {
653: boolean filterRedirectorDefined = false;
654: boolean jspRedirectorDefined = false;
655: boolean servletRedirectorDefined = false;
656:
657: // add the user defined redirectors
658: for (Iterator i = this .redirectors.iterator(); i.hasNext();) {
659: Redirector redirector = (Redirector) i.next();
660: if (redirector instanceof FilterRedirector) {
661: filterRedirectorDefined = true;
662: } else if (redirector instanceof JspRedirector) {
663: jspRedirectorDefined = true;
664: } else if (redirector instanceof ServletRedirector) {
665: servletRedirectorDefined = true;
666: }
667: redirector.mergeInto(theWebXml);
668: }
669:
670: // now add the default redirectors if they haven't been provided by
671: // the user
672: if (!filterRedirectorDefined) {
673: new FilterRedirector().mergeInto(theWebXml);
674: }
675: if (!servletRedirectorDefined) {
676: new ServletRedirector().mergeInto(theWebXml);
677: }
678: if (!jspRedirectorDefined) {
679: new JspRedirector().mergeInto(theWebXml);
680: }
681: }
682:
683: /**
684: * Enhances the provided web deployment descriptor with the definitions
685: * required for testing with Cactus.
686: *
687: * @param theWebXml The original deployment descriptor
688: * @return A temporary file containing the cactified descriptor
689: */
690: private File cactifyWebXml(WebXml theWebXml) {
691: addRedirectorDefinitions(theWebXml);
692: addJspRedirector();
693: addEjbRefs(theWebXml);
694:
695: // If the user has specified a deployment descriptor to merge into the
696: // cactified descriptor, perform the merge
697: if (this .mergeWebXml != null) {
698: try {
699: WebXml parsedMergeWebXml = WebXmlIo
700: .parseWebXmlFromFile(this .mergeWebXml,
701: this .xmlCatalog);
702: WebXmlMerger merger = new WebXmlMerger(theWebXml);
703: merger.setMonitor(new AntMonitor(this ));
704: merger.merge(parsedMergeWebXml);
705: } catch (IOException e) {
706: throw new BuildException(
707: "Could not merge deployment descriptors", e);
708: } catch (SAXException e) {
709: throw new BuildException(
710: "Parsing of merge file failed", e);
711: } catch (ParserConfigurationException e) {
712: throw new BuildException(
713: "XML parser configuration error", e);
714: }
715: }
716:
717: // Serialize the cactified deployment descriptor into a temporary file,
718: // so that it can get picked up by the War task
719: FileUtils fileUtils = FileUtils.newFileUtils();
720: File tmpDir = fileUtils.createTempFile("cactus", "tmp.dir",
721: getProject().getBaseDir());
722: tmpDir.mkdirs();
723: tmpDir.deleteOnExit();
724: File webXmlFile = null;
725: try {
726: ZipFileSet fileSet = new ZipFileSet();
727: fileSet.setDir(tmpDir);
728: File[] files = WebXmlIo.writeAll(theWebXml, tmpDir);
729: for (int i = 0; i < files.length; i++) {
730: File f = files[i];
731: f.deleteOnExit();
732: if (f.getName().equals("web.xml")) {
733: webXmlFile = f;
734: } else {
735: fileSet.createInclude().setName(f.getName());
736: }
737: }
738: addWebinf(fileSet);
739: } catch (IOException ioe) {
740: throw new BuildException(
741: "Could not write temporary deployment descriptor",
742: ioe);
743: }
744: return webXmlFile;
745: }
746:
747: /**
748: * Extracts and parses the original web deployment descriptor from the
749: * web-app.
750: *
751: * @return The parsed descriptor or null if not found
752: * @throws BuildException If the descriptor could not be
753: * parsed
754: */
755: private WebXml getOriginalWebXml() throws BuildException {
756: // Open the archive as JAR file and extract the deployment descriptor
757: WarArchive war = null;
758: try {
759: war = new DefaultWarArchive(this .srcFile);
760: WebXml webXml = war.getWebXml();
761: return webXml;
762: } catch (SAXException e) {
763: throw new BuildException(
764: "Parsing of web.xml deployment descriptor failed",
765: e);
766: } catch (IOException e) {
767: throw new BuildException("Failed to open WAR", e);
768: } catch (ParserConfigurationException e) {
769: throw new BuildException("XML parser configuration error",
770: e);
771: }
772: }
773:
774: /**
775: * Add ejb references to a web.xml.
776: *
777: * @param theWebXml the web.xml to modify
778: */
779: private void addEjbRefs(WebXml theWebXml) {
780: Iterator i = ejbRefs.iterator();
781: while (i.hasNext()) {
782: EjbRef ref = (EjbRef) i.next();
783: if ("Session".equals(ref.getType())) {
784: theWebXml.addLocalSessionEjbRef(ref.getName(), ref
785: .getLocalInterface(), ref
786: .getLocalHomeInterface(), ref.getJndiName());
787: } else if ("Entity".equals(ref.getType())) {
788: theWebXml.addLocalEntityEjbRef(ref.getName(), ref
789: .getLocalInterface(), ref
790: .getLocalHomeInterface(), ref.getJndiName());
791: }
792: }
793: }
794: }
|