001: /*
002: * ========================================================================
003: *
004: * Copyright 2003-2004 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.container.jboss;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.io.StringReader;
025: import java.util.jar.Attributes;
026: import java.util.jar.JarFile;
027: import java.util.jar.Manifest;
028: import java.util.zip.ZipEntry;
029:
030: import javax.xml.parsers.DocumentBuilder;
031: import javax.xml.parsers.DocumentBuilderFactory;
032: import javax.xml.parsers.ParserConfigurationException;
033:
034: import org.apache.cactus.integration.ant.container.AbstractJavaContainer;
035: import org.apache.tools.ant.BuildException;
036: import org.apache.tools.ant.taskdefs.Copy;
037: import org.apache.tools.ant.taskdefs.Java;
038: import org.apache.tools.ant.types.FileSet;
039: import org.apache.tools.ant.types.Path;
040: import org.apache.tools.ant.util.FileUtils;
041: import org.w3c.dom.Document;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.Node;
044: import org.xml.sax.EntityResolver;
045: import org.xml.sax.InputSource;
046: import org.xml.sax.SAXException;
047:
048: /**
049: * Special container support for the JBoss application server.
050: *
051: * @version $Id: JBoss3xContainer.java 239130 2005-01-29 15:49:18Z vmassol $
052: */
053: public class JBoss3xContainer extends AbstractJavaContainer {
054: // Instance Variables ------------------------------------------------------
055:
056: /**
057: * The JBoss 3.x installation directory.
058: */
059: private File dir;
060:
061: /**
062: * The name of the server configuration to use for running the tests.
063: */
064: private String config = "default";
065:
066: /**
067: * The location of a directory where to find the JBoss server
068: * configurations.
069: */
070: private File configDir;
071:
072: /**
073: * The port to which the container should be bound.
074: */
075: private int port = 8080;
076:
077: /**
078: * The server JNDI Port (used during shutdown)
079: */
080: private int jndiPort = 1099;
081:
082: /**
083: * The JBoss version detected by reading the Manifest file in the
084: * installation directory.
085: */
086: private String version;
087:
088: /**
089: * The context root of the tested application.
090: */
091: private String testContextRoot;
092:
093: /**
094: * The temporary directory from which the container will be started.
095: */
096: private File tmpDir;
097:
098: // Public Methods ----------------------------------------------------------
099:
100: /**
101: * Sets the JBoss installation directory.
102: *
103: * @param theDir The directory to set
104: * @throws BuildException If the specified directory doesn't contain a valid
105: * JBoss 3.x installation
106: */
107: public final void setDir(File theDir) throws BuildException {
108: this .dir = theDir;
109: }
110:
111: /**
112: * Sets the name of the server configuration to use for running the tests.
113: *
114: * @param theConfig The configuration name
115: */
116: public final void setConfig(String theConfig) {
117: this .config = theConfig;
118: }
119:
120: /**
121: * Sets the location of the server configuration directory.
122: *
123: * @param theConfigDir The configuration directory
124: */
125: public final void setConfigDir(File theConfigDir) {
126: this .configDir = theConfigDir;
127: }
128:
129: /**
130: * Sets the port that will be used to poll the server to verify if
131: * it is started. This is needed for the use case where the user
132: * has defined his own JBoss configuration by using the
133: * {@link #setConfig(String)} call and has defined a port other
134: * than the default one.
135: *
136: * Note: This value is not yet used to set the port
137: * to which the container will listen to. The reason is that this is
138: * hard to implement with JBoss and nobody had the courage to implement
139: * it yet...
140: *
141: * @param thePort The port to set
142: */
143: public final void setPort(int thePort) {
144: this .port = thePort;
145: }
146:
147: /**
148: * Specify the JNDI port to use.
149: *
150: * @param theJndiPort The JNDI port
151: */
152: public final void setJndiPort(int theJndiPort) {
153: this .jndiPort = theJndiPort;
154: }
155:
156: /**
157: * Sets the temporary directory from which the container is run.
158: *
159: * @param theTmpDir The temporary directory to set
160: */
161: public final void setTmpDir(File theTmpDir) {
162: this .tmpDir = theTmpDir;
163: }
164:
165: // Container Implementation ------------------------------------------------
166:
167: /**
168: * @see AbstractJavaContainer#getTestContext()
169: */
170: public String getTestContext() {
171: return this .testContextRoot;
172: }
173:
174: /**
175: * @see AbstractJavaContainer#getName()
176: */
177: public final String getName() {
178: return "JBoss " + this .version;
179: }
180:
181: /**
182: * @see AbstractJavaContainer#getPort()
183: */
184: public final int getPort() {
185: return this .port;
186: }
187:
188: /**
189: * Returns the server JNDI port.
190: *
191: * @return The JNDI port
192: */
193: public final int getJndiPort() {
194: return this .jndiPort;
195: }
196:
197: /**
198: * @return The temporary directory from which the container will be
199: * started.
200: */
201: protected final File getTmpDir() {
202: return this .tmpDir;
203: }
204:
205: /**
206: * @see AbstractJavaContainer#init()
207: */
208: public final void init() {
209: // Verify the installation directory
210: this .version = getVersion(this .dir);
211: if (this .version == null) {
212: throw new BuildException(this .dir
213: + " not recognized as a JBoss 3.x installation");
214: }
215: if (!this .version.startsWith("3")) {
216: throw new BuildException(
217: "This element doesn't support version "
218: + this .version + " of JBoss");
219: }
220:
221: // Try to infer the test root context from the JBoss specific
222: // <code>jboss-web.xml</code> file.
223: this .testContextRoot = getTestContextFromJBossWebXml();
224:
225: // TODO: as long as we don't have a way to set the port on the JBoss
226: // instance, we'll at least need to extract the port from a config file
227: // in the installation directory
228: }
229:
230: /**
231: * @see AbstractJavaContainer#startUp()
232: */
233: public final void startUp() {
234: try {
235: // TODO: It seems JBoss 3.2.x does not support server
236: // configurations located in directories with spaces in their name.
237: // Thus we define the default tmp dir to default to where default
238: // JBoss server configurations are located. This should be removed
239: // once we find out how to make JBoss work when using the default
240: // tmp dir of System.getProperty("java.io.tmpdir")
241: if (getTmpDir() == null) {
242: setTmpDir(new File(this .dir, "server/cactus"));
243: }
244:
245: File customServerDir = setupTempDirectory(getTmpDir(),
246: "cactus/jboss3x");
247: cleanTempDirectory(customServerDir);
248:
249: prepare("cactus/jboss3x", customServerDir);
250:
251: File binDir = new File(this .dir, "bin");
252:
253: Java java = createJavaForStartUp();
254: java.setDir(binDir);
255:
256: java.addSysproperty(createSysProperty("program.name",
257: new File(binDir, "run.bat")));
258: java.addSysproperty(createSysProperty(
259: "jboss.server.home.dir", customServerDir));
260: java.addSysproperty(createSysProperty(
261: "jboss.server.home.url", customServerDir.toURL()
262: .toString()));
263:
264: Path classpath = java.createClasspath();
265: classpath.createPathElement().setLocation(
266: new File(binDir, "run.jar"));
267: addToolsJarToClasspath(classpath);
268: java.setClassname("org.jboss.Main");
269: java.createArg().setValue("-c");
270: java.createArg().setValue(this .config);
271: java.execute();
272: } catch (IOException ioe) {
273: getLog().error("Failed to startup the container", ioe);
274: throw new BuildException(ioe);
275: }
276: }
277:
278: /**
279: * @see AbstractJavaContainer#shutDown()
280: */
281: public final void shutDown() {
282: File binDir = new File(this .dir, "bin");
283:
284: Java java = createJavaForShutDown();
285: java.setFork(true);
286:
287: Path classPath = java.createClasspath();
288: classPath.createPathElement().setLocation(
289: new File(binDir, "shutdown.jar"));
290:
291: java.setClassname("org.jboss.Shutdown");
292:
293: if (this .version.startsWith("3.2")) {
294: java.createArg().setValue(
295: "--server=" + this .getServer() + ":"
296: + this .getJndiPort());
297: java.createArg().setValue("--shutdown");
298: } else {
299: java.createArg().setValue(this .getServer());
300: java.createArg().setValue(String.valueOf(getPort()));
301: }
302: java.execute();
303: }
304:
305: // Private Methods ---------------------------------------------------------
306:
307: /**
308: * @return the test context from JBoss's <code>jboss-web.xml</code> or null
309: * if none has been defined or if the file doesn't exist
310: */
311: private String getTestContextFromJBossWebXml() {
312: String testContext = null;
313:
314: try {
315: Document doc = getJBossWebXML();
316: Element root = doc.getDocumentElement();
317: Node context = root.getElementsByTagName("context-root")
318: .item(0);
319: testContext = context.getFirstChild().getNodeValue();
320: } catch (Exception e) {
321: // no worries if we can't find what we are looking for (for now).
322: }
323:
324: return testContext;
325: }
326:
327: /**
328: * Prepares a temporary installation of the container and deploys the
329: * web-application.
330: *
331: * @param theDirName The name of the temporary container installation
332: * directory
333: * @param theCustomServerDir the directory where the JBoss server
334: * configuration is to be deployed
335: * @throws IOException If an I/O error occurs
336: */
337: private void prepare(String theDirName, File theCustomServerDir)
338: throws IOException {
339: FileUtils fileUtils = FileUtils.newFileUtils();
340:
341: // If the configDir property has not been set, let's default it to
342: // the default JBoss server configuration directory.
343: File computedConfigDir;
344: if (this .configDir == null) {
345: computedConfigDir = new File(this .dir, "server");
346: } else {
347: computedConfigDir = this .configDir;
348: }
349:
350: // Copy the default JBoss server config directory into our custom
351: // server directory.
352: Copy copy = new Copy();
353: copy.setTaskName("cactus");
354: copy.setProject(getProject());
355: copy.setTodir(theCustomServerDir);
356: FileSet srcFiles = new FileSet();
357: srcFiles.setDir(new File(computedConfigDir, this .config));
358: copy.addFileset(srcFiles);
359: copy.execute();
360:
361: // Deploy the web-app by copying the WAR file into the webapps
362: // directory
363: File deployDir = new File(theCustomServerDir, "/deploy");
364: fileUtils.copyFile(getDeployableFile().getFile(), new File(
365: deployDir, getDeployableFile().getFile().getName()),
366: null, true);
367: }
368:
369: /**
370: * Returns the version of the JBoss installation.
371: *
372: * @param theDir The JBoss installation directory
373: * @return The JBoss version, or <code>null</code> if the verion number
374: * could not be retrieved
375: */
376: private String getVersion(File theDir) {
377: // Extract version information from the manifest in run.jar
378: String retVal = null;
379: try {
380: JarFile runJar = new JarFile(
381: new File(theDir, "bin/run.jar"));
382: Manifest mf = runJar.getManifest();
383: if (mf != null) {
384: Attributes attrs = mf.getMainAttributes();
385: retVal = attrs
386: .getValue(Attributes.Name.SPECIFICATION_VERSION);
387: } else {
388: getLog().warn("Couldn't find MANIFEST.MF in " + runJar);
389: }
390: } catch (IOException ioe) {
391: getLog().warn(
392: "Couldn't retrieve JBoss version information", ioe);
393: }
394: return retVal;
395: }
396:
397: /**
398: * Get a Document object for the <code>jboss-web.xml</code> file.
399: *
400: * @return The parsed XML Document object or null if not found
401: * @throws IOException If there is a problem reading files
402: * @throws ParserConfigurationException If there is a problem w/ parser
403: * @throws SAXException If there is a problem with parsing
404: */
405: private Document getJBossWebXML() throws IOException,
406: ParserConfigurationException, SAXException {
407: Document doc = null;
408: File configDir = new File(this .dir, "server");
409: File deployDir = new File(configDir, this .config + "/deploy");
410: File warFile = new File(deployDir, getDeployableFile()
411: .getFile().getName());
412:
413: JarFile war = new JarFile(warFile);
414: ZipEntry entry = war.getEntry("WEB-INF/jboss-web.xml");
415: if (entry != null) {
416: DocumentBuilderFactory factory = DocumentBuilderFactory
417: .newInstance();
418: factory.setValidating(false);
419: factory.setNamespaceAware(false);
420:
421: DocumentBuilder builder = factory.newDocumentBuilder();
422: builder.setEntityResolver(new EntityResolver() {
423: public InputSource resolveEntity(String thePublicId,
424: String theSystemId) throws SAXException {
425: return new InputSource(new StringReader(""));
426: }
427: });
428: doc = builder.parse(war.getInputStream(entry));
429: }
430: war.close();
431: return doc;
432: }
433:
434: }
|