001: /*
002: * ========================================================================
003: *
004: * Copyright 2003-2005 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.net.MalformedURLException;
024: import java.net.URL;
025: import java.util.ArrayList;
026: import java.util.Enumeration;
027: import java.util.List;
028: import java.util.ResourceBundle;
029:
030: import org.apache.cactus.integration.ant.container.Container;
031: import org.apache.cactus.integration.ant.container.ContainerRunner;
032: import org.apache.cactus.integration.ant.deployment.DeployableFile;
033: import org.apache.cactus.integration.ant.deployment.EarParser;
034: import org.apache.cactus.integration.ant.deployment.WarParser;
035: import org.apache.cactus.integration.ant.util.AntLog;
036: import org.apache.cactus.integration.ant.util.AntTaskFactory;
037: import org.apache.cactus.integration.ant.util.DefaultAntTaskFactory;
038: import org.apache.cactus.integration.ant.util.PropertySet;
039: import org.apache.tools.ant.BuildException;
040: import org.apache.tools.ant.Project;
041: import org.apache.tools.ant.taskdefs.optional.junit.JUnitTask;
042: import org.apache.tools.ant.taskdefs.optional.junit.JUnitTest;
043: import org.apache.tools.ant.types.Environment;
044: import org.apache.tools.ant.types.Path;
045: import org.apache.tools.ant.types.Environment.Variable;
046:
047: /**
048: * An Ant task that extends the optional JUnit task to provide support for
049: * in-container testing.
050: *
051: * @version $Id: CactusTask.java 239130 2005-01-29 15:49:18Z vmassol $
052: */
053: public class CactusTask extends JUnitTask {
054: // Instance Variables ------------------------------------------------------
055:
056: /**
057: * The nested containerset element.
058: */
059: private ContainerSet containerSet;
060:
061: /**
062: * The archive that contains the enterprise application that should be
063: * tested.
064: */
065: private File earFile;
066:
067: /**
068: * The archive that contains the web-app that is ready to be tested.
069: */
070: private File warFile;
071:
072: /**
073: * System properties that will be set in the container JVM.
074: */
075: private List systemProperties = new ArrayList();
076:
077: /**
078: * Additional classpath entries for the classpath that will be used to
079: * start the containers.
080: */
081: private Path containerClasspath;
082:
083: // Constructors ------------------------------------------------------------
084:
085: /**
086: * Constructor.
087: *
088: * @throws Exception If the constructor of JUnitTask throws an exception
089: */
090: public CactusTask() throws Exception {
091: // TODO: Fix comment for this constructor as it doesn't seem quite
092: // right. Explain why we don't call the super constructor?
093: }
094:
095: // Public Methods ----------------------------------------------------------
096:
097: /**
098: * @see org.apache.tools.ant.Task#init()
099: */
100: public void init() {
101: super .init();
102:
103: addClasspathEntry("/org/aspectj/lang/JoinPoint.class");
104: addClasspathEntry("/org/apache/cactus/ServletTestCase.class");
105: addClasspathEntry("/org/apache/cactus/integration/ant/CactusTask.class");
106: addClasspathEntry("/org/apache/commons/logging/Log.class");
107: addClasspathEntry("/org/apache/commons/httpclient/HttpClient.class");
108: }
109:
110: /**
111: * @see org.apache.tools.ant.Task#execute()
112: */
113: public void execute() throws BuildException {
114: if ((this .warFile == null) && (this .earFile == null)) {
115: throw new BuildException(
116: "You must specify either the [warfile] or "
117: + "the [earfile] attribute");
118: }
119:
120: if ((this .warFile != null) && (this .earFile != null)) {
121: throw new BuildException(
122: "You must specify either the [warfile] or "
123: + "the [earfile] attribute but not both");
124: }
125:
126: // Parse deployment descriptors for WAR or EAR files
127: DeployableFile deployableFile;
128: if (this .warFile != null) {
129: deployableFile = WarParser.parse(this .warFile);
130: } else {
131: deployableFile = EarParser.parse(this .earFile);
132: }
133:
134: addRedirectorNameProperties(deployableFile);
135:
136: if (this .containerSet == null) {
137: log("No containers specified, tests will run locally",
138: Project.MSG_VERBOSE);
139: super .execute();
140: } else {
141: Container[] containers = this .containerSet.getContainers();
142: Variable contextUrl = new Variable();
143: contextUrl.setKey("cactus.contextURL");
144: addSysproperty(contextUrl);
145:
146: AntTaskFactory antTaskFactory = new DefaultAntTaskFactory(
147: getProject(), getTaskName(), getLocation(),
148: getOwningTarget());
149:
150: for (int i = 0; i < containers.length; i++) {
151: containers[i].setAntTaskFactory(antTaskFactory);
152: containers[i].setLog(new AntLog(this ));
153:
154: // Clone the DeployableFile instance as each container can
155: // override default deployment properties (e.g. port, context
156: // root, etc).
157: DeployableFile this Deployable = null;
158: try {
159: this Deployable = (DeployableFile) deployableFile
160: .clone();
161: } catch (CloneNotSupportedException e) {
162: throw new BuildException(e);
163: }
164: containers[i].setDeployableFile(this Deployable);
165:
166: // Allow the container to override the default test context.
167: // This is to support container extensions to the web.xml file.
168: // Most containers allow defining the root context in these
169: // extensions.
170: if (containers[i].getTestContext() != null) {
171: this Deployable.setTestContext(containers[i]
172: .getTestContext());
173: }
174:
175: containers[i]
176: .setSystemProperties((Variable[]) this .systemProperties
177: .toArray(new Variable[0]));
178:
179: // Add extra classpath entries
180: containers[i]
181: .setContainerClasspath(this .containerClasspath);
182:
183: if (containers[i].isEnabled()) {
184: containers[i].init();
185: log(
186: "--------------------------------------------------"
187: + "---------------",
188: Project.MSG_INFO);
189: log("Running tests against "
190: + containers[i].getName() + " @ "
191: + containers[i].getBaseURL(),
192: Project.MSG_INFO);
193: log(
194: "--------------------------------------------------"
195: + "---------------",
196: Project.MSG_INFO);
197: contextUrl.setValue(containers[i].getBaseURL()
198: + "/" + this Deployable.getTestContext());
199: executeInContainer(containers[i], this Deployable);
200: }
201: }
202: }
203: }
204:
205: /**
206: * Adds the nested containers element (only one is permitted).
207: *
208: * @param theContainerSet The nested element to add
209: */
210: public final void addContainerSet(ContainerSet theContainerSet) {
211: if (this .containerSet != null) {
212: throw new BuildException(
213: "Only one nested containerset element supported");
214: }
215: this .containerSet = theContainerSet;
216: }
217:
218: /**
219: * Sets the enterprise application archive that will be tested. It must
220: * already contain the test-cases and the required libraries as a web
221: * module.
222: *
223: * @param theEarFile The EAR file to set
224: */
225: public final void setEarFile(File theEarFile) {
226: if (this .warFile != null) {
227: throw new BuildException(
228: "You may only specify one of [earfile] and [warfile]");
229: }
230: this .earFile = theEarFile;
231: }
232:
233: /**
234: * Sets the web application archive that will be tested. It must already
235: * contain the test-cases and the required libraries.
236: *
237: * @param theWarFile The WAR file to set
238: */
239: public final void setWarFile(File theWarFile) {
240: if (this .earFile != null) {
241: throw new BuildException(
242: "You may only specify one of [earfile] and [warfile]");
243: }
244: this .warFile = theWarFile;
245: }
246:
247: /**
248: * Adds a system property to both client side and server side JVMs.
249: * @see JUnitTask#addSysproperty(Environment.Variable)
250: */
251: public void addSysproperty(Environment.Variable theProperty) {
252: addCactusServerProperty(theProperty);
253: super .addSysproperty(theProperty);
254: }
255:
256: /**
257: * Called by Ant when the Variable object has been properly initialized.
258: *
259: * @param theProperty the system property to set
260: */
261: public void addConfiguredSysproperty(
262: Environment.Variable theProperty) {
263: addSysproperty(theProperty);
264: }
265:
266: /**
267: * Adds a set of properties that will be used as system properties
268: * either on the client side or on the server side.
269: *
270: * @param thePropertySet the set of properties to be added
271: */
272: public void addConfiguredCactusproperty(PropertySet thePropertySet) {
273: // Add all properties from the properties file
274: ResourceBundle bundle = thePropertySet.readProperties();
275: Enumeration keys = bundle.getKeys();
276: while (keys.hasMoreElements()) {
277: String key = (String) keys.nextElement();
278: Variable var = new Variable();
279: var.setKey(key);
280: var.setValue(bundle.getString(key));
281: if (thePropertySet.isServer()) {
282: addCactusServerProperty(var);
283: } else {
284: super .addSysproperty(var);
285: }
286: }
287: }
288:
289: /**
290: * Adds container classpath to the classpath that will be used for starting
291: * the container.
292: *
293: * @return reference to the classpath
294: * @since Cactus 1.6
295: */
296: public Path createContainerClasspath() {
297: if (this .containerClasspath == null) {
298: this .containerClasspath = new Path(this .project);
299: }
300:
301: return this .containerClasspath.createPath();
302: }
303:
304: // Private Methods ---------------------------------------------------------
305:
306: /**
307: * Adds a Cactus system property for the client side JVM.
308: *
309: * @param theKey The property name
310: * @param theValue The property value
311: */
312: private void addCactusClientProperty(String theKey, String theValue) {
313: log("Adding Cactus client system property [" + theKey
314: + "] with value [" + theValue + "]",
315: Project.MSG_VERBOSE);
316: Variable sysProperty = new Variable();
317: sysProperty.setKey(theKey);
318: sysProperty.setValue(theValue);
319: super .addSysproperty(sysProperty);
320: }
321:
322: /**
323: * Adds a Cactus system property for the server side JVM.
324: *
325: * @param theProperty The system property to set in the container JVM
326: */
327: private void addCactusServerProperty(Variable theProperty) {
328: log("Adding Cactus server system property ["
329: + theProperty.getKey() + "] with value ["
330: + theProperty.getValue() + "]", Project.MSG_VERBOSE);
331: this .systemProperties.add(theProperty);
332: }
333:
334: /**
335: * Adds a Cactus system property for the server side JVM.
336: *
337: * @param theKey The property name
338: * @param theValue The property value
339: */
340: private void addCactusServerProperty(String theKey, String theValue) {
341: Variable property = new Variable();
342: property.setKey(theKey);
343: property.setValue(theValue);
344: addCactusServerProperty(property);
345: }
346:
347: /**
348: * Extracts the redirector mappings from the deployment descriptor and sets
349: * the corresponding system properties.
350: *
351: * @param theFile The file to deploy in the container
352: */
353: private void addRedirectorNameProperties(DeployableFile theFile) {
354: String filterRedirectorMapping = theFile
355: .getFilterRedirectorMapping();
356: if (filterRedirectorMapping != null) {
357: addCactusClientProperty("cactus.filterRedirectorName",
358: filterRedirectorMapping.substring(1));
359: } else {
360: log("No mapping of the filter redirector found",
361: Project.MSG_VERBOSE);
362: }
363:
364: String jspRedirectorMapping = theFile.getJspRedirectorMapping();
365: if (jspRedirectorMapping != null) {
366: addCactusClientProperty("cactus.jspRedirectorName",
367: jspRedirectorMapping.substring(1));
368: } else {
369: log("No mapping of the JSP redirector found",
370: Project.MSG_VERBOSE);
371: }
372:
373: String servletRedirectorMapping = theFile
374: .getServletRedirectorMapping();
375: if (servletRedirectorMapping != null) {
376: addCactusClientProperty("cactus.servletRedirectorName",
377: servletRedirectorMapping.substring(1));
378: } else {
379: throw new BuildException("The WAR has not been cactified");
380: }
381: }
382:
383: /**
384: * Executes the unit tests in the given container.
385: *
386: * @param theContainer The container to run the tests against
387: * @param theFile the file to deploy in the container
388: */
389: private void executeInContainer(Container theContainer,
390: DeployableFile theFile) {
391: log("Starting up container", Project.MSG_VERBOSE);
392: ContainerRunner runner = new ContainerRunner(theContainer);
393: runner.setLog(new AntLog(this ));
394: try {
395: URL url = new URL(theContainer.getBaseURL() + "/"
396: + theFile.getTestContext()
397: + theFile.getServletRedirectorMapping()
398: + "?Cactus_Service=RUN_TEST");
399: runner.setURL(url);
400: if (this .containerSet.getTimeout() > 0) {
401: runner.setTimeout(this .containerSet.getTimeout());
402: }
403: runner.startUpContainer();
404: log("Server name retrieved from 'Server' HTTP header: ["
405: + runner.getServerName() + "]", Project.MSG_VERBOSE);
406: try {
407: Enumeration tests = getIndividualTests();
408: while (tests.hasMoreElements()) {
409: JUnitTest test = (JUnitTest) tests.nextElement();
410: if (test.shouldRun(getProject())
411: && !theContainer.isExcluded(test.getName())) {
412: if (theContainer.getToDir() != null) {
413: test.setTodir(theContainer.getToDir());
414: }
415: execute(test);
416: }
417: }
418: } finally {
419: log("Shutting down container", Project.MSG_VERBOSE);
420: runner.shutDownContainer();
421: log("Container shut down", Project.MSG_VERBOSE);
422: }
423: } catch (MalformedURLException mue) {
424: throw new BuildException("Malformed test URL", mue);
425: }
426: }
427: }
|