001: /*
002: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/core/StandardHostDeployer.java,v 1.10 2002/08/01 01:41:43 amyroh Exp $
003: * $Revision: 1.10 $
004: * $Date: 2002/08/01 01:41:43 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 1999 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: * [Additional notices, if required by prior licensing conditions]
061: *
062: */
063:
064: package org.apache.catalina.core;
065:
066: import java.io.BufferedOutputStream;
067: import java.io.File;
068: import java.io.FileOutputStream;
069: import java.io.InputStream;
070: import java.io.IOException;
071: import java.net.URL;
072: import java.util.Enumeration;
073: import org.apache.catalina.Container;
074: import org.apache.catalina.Context;
075: import org.apache.catalina.Deployer;
076: import org.apache.catalina.Globals;
077: import org.apache.catalina.Lifecycle;
078: import org.apache.catalina.LifecycleException;
079: import org.apache.catalina.LifecycleListener;
080: import org.apache.catalina.startup.ContextRuleSet;
081: import org.apache.catalina.startup.NamingRuleSet;
082: import org.apache.catalina.util.StringManager;
083: import org.apache.commons.digester.Digester;
084: import org.xml.sax.SAXParseException;
085:
086: /**
087: * <p>Implementation of <b>Deployer</b> that is delegated to by the
088: * <code>StandardHost</code> implementation class.</p>
089: *
090: * @author Craig R. McClanahan
091: * @version $Revision: 1.10 $ $Date: 2002/08/01 01:41:43 $
092: */
093:
094: public class StandardHostDeployer implements Deployer {
095:
096: // ----------------------------------------------------------- Constructors
097:
098: /**
099: * Create a new StandardHostDeployer associated with the specified
100: * StandardHost.
101: *
102: * @param host The StandardHost we are associated with
103: */
104: public StandardHostDeployer(StandardHost host) {
105:
106: super ();
107: this .host = host;
108:
109: }
110:
111: // ----------------------------------------------------- Instance Variables
112:
113: /**
114: * The <code>Context</code> that was added via a call to
115: * <code>addChild()</code> while parsing the configuration descriptor.
116: */
117: private Context context = null;
118:
119: /**
120: * The <code>Digester</code> instance to use for deploying web applications
121: * to this <code>Host</code>. <strong>WARNING</strong> - Usage of this
122: * instance must be appropriately synchronized to prevent simultaneous
123: * access by multiple threads.
124: */
125: private Digester digester = null;
126:
127: /**
128: * The <code>ContextRuleSet</code> associated with our
129: * <code>digester</code> instance.
130: */
131: private ContextRuleSet contextRuleSet = null;
132:
133: /**
134: * The <code>StandardHost</code> instance we are associated with.
135: */
136: protected StandardHost host = null;
137:
138: /**
139: * The <code>NamingRuleSet</code> associated with our
140: * <code>digester</code> instance.
141: */
142: private NamingRuleSet namingRuleSet = null;
143:
144: /**
145: * The document base which should replace the value specified in the
146: * <code>Context</code> being added in the <code>addChild()</code> method,
147: * or <code>null</code> if the original value should remain untouched.
148: */
149: private String overrideDocBase = null;
150:
151: /**
152: * The string manager for this package.
153: */
154: protected static StringManager sm = StringManager
155: .getManager(Constants.Package);
156:
157: // -------------------------------------------------------- Depoyer Methods
158:
159: /**
160: * Return the name of the Container with which this Deployer is associated.
161: */
162: public String getName() {
163:
164: return (host.getName());
165:
166: }
167:
168: /**
169: * Install a new web application, whose web application archive is at the
170: * specified URL, into this container with the specified context path.
171: * A context path of "" (the empty string) should be used for the root
172: * application for this container. Otherwise, the context path must
173: * start with a slash.
174: * <p>
175: * If this application is successfully installed, a ContainerEvent of type
176: * <code>PRE_INSTALL_EVENT</code> will be sent to registered listeners
177: * before the associated Context is started, and a ContainerEvent of type
178: * <code>INSTALL_EVENT</code> will be sent to all registered listeners
179: * after the associated Context is started, with the newly created
180: * <code>Context</code> as an argument.
181: *
182: * @param contextPath The context path to which this application should
183: * be installed (must be unique)
184: * @param war A URL of type "jar:" that points to a WAR file, or type
185: * "file:" that points to an unpacked directory structure containing
186: * the web application to be installed
187: *
188: * @exception IllegalArgumentException if the specified context path
189: * is malformed (it must be "" or start with a slash)
190: * @exception IllegalStateException if the specified context path
191: * is already attached to an existing web application
192: * @exception IOException if an input/output error was encountered
193: * during installation
194: */
195: public synchronized void install(String contextPath, URL war)
196: throws IOException {
197:
198: // Validate the format and state of our arguments
199: if (contextPath == null)
200: throw new IllegalArgumentException(sm
201: .getString("standardHost.pathRequired"));
202: if (!contextPath.equals("") && !contextPath.startsWith("/"))
203: throw new IllegalArgumentException(sm.getString(
204: "standardHost.pathFormat", contextPath));
205: if (findDeployedApp(contextPath) != null)
206: throw new IllegalStateException(sm.getString(
207: "standardHost.pathUsed", contextPath));
208: if (war == null)
209: throw new IllegalArgumentException(sm
210: .getString("standardHost.warRequired"));
211:
212: // Calculate the document base for the new web application
213: host.log(sm.getString("standardHost.installing", contextPath,
214: war.toString()));
215: String url = war.toString();
216: String docBase = null;
217: if (url.startsWith("jar:")) {
218: url = url.substring(4, url.length() - 2);
219: }
220: if (url.startsWith("file://"))
221: docBase = url.substring(7);
222: else if (url.startsWith("file:"))
223: docBase = url.substring(5);
224: else
225: throw new IllegalArgumentException(sm.getString(
226: "standardHost.warURL", url));
227:
228: // Install the new web application
229: try {
230: Class clazz = Class.forName(host.getContextClass());
231: Context context = (Context) clazz.newInstance();
232: context.setPath(contextPath);
233: context.setDocBase(docBase);
234: if (context instanceof Lifecycle) {
235: clazz = Class.forName(host.getConfigClass());
236: LifecycleListener listener = (LifecycleListener) clazz
237: .newInstance();
238: ((Lifecycle) context).addLifecycleListener(listener);
239: }
240: host.fireContainerEvent(PRE_INSTALL_EVENT, context);
241: host.addChild(context);
242: host.fireContainerEvent(INSTALL_EVENT, context);
243: } catch (Exception e) {
244: host.log(sm.getString("standardHost.installError",
245: contextPath), e);
246: throw new IOException(e.toString());
247: }
248:
249: }
250:
251: /**
252: * <p>Install a new web application, whose context configuration file
253: * (consisting of a <code><Context></code> element) and (optional)
254: * web application archive are at the specified URLs.</p>
255: *
256: * If this application is successfully installed, a ContainerEvent of type
257: * <code>PRE_INSTALL_EVENT</code> will be sent to registered listeners
258: * before the associated Context is started, and a ContainerEvent of type
259: * <code>INSTALL_EVENT</code> will be sent to all registered listeners
260: * after the associated Context is started, with the newly created
261: * <code>Context</code> as an argument.
262: *
263: * @param config A URL that points to the context configuration descriptor
264: * to be used for configuring the new Context
265: * @param war A URL of type "jar:" that points to a WAR file, or type
266: * "file:" that points to an unpacked directory structure containing
267: * the web application to be installed, or <code>null</code> to use
268: * the <code>docBase</code> attribute from the configuration descriptor
269: *
270: * @exception IllegalArgumentException if one of the specified URLs is
271: * null
272: * @exception IllegalStateException if the context path specified in the
273: * context configuration file is already attached to an existing web
274: * application
275: * @exception IOException if an input/output error was encountered
276: * during installation
277: */
278: public synchronized void install(URL config, URL war)
279: throws IOException {
280:
281: // Validate the format and state of our arguments
282: if (config == null)
283: throw new IllegalArgumentException(sm
284: .getString("standardHost.configRequired"));
285:
286: if (!host.isDeployXML())
287: throw new IllegalArgumentException(sm
288: .getString("standardHost.configNotAllowed"));
289:
290: // Calculate the document base for the new web application (if needed)
291: String docBase = null; // Optional override for value in config file
292: if (war != null) {
293: String url = war.toString();
294: host.log(sm.getString("standardHost.installingWAR", url));
295: // Calculate the WAR file absolute pathname
296: if (url.startsWith("jar:")) {
297: url = url.substring(4, url.length() - 2);
298: }
299: if (url.startsWith("file://"))
300: docBase = url.substring(7);
301: else if (url.startsWith("file:"))
302: docBase = url.substring(5);
303: else
304: throw new IllegalArgumentException(sm.getString(
305: "standardHost.warURL", url));
306:
307: }
308:
309: // Install the new web application
310: this .context = null;
311: this .overrideDocBase = docBase;
312: InputStream stream = null;
313: try {
314: stream = config.openStream();
315: Digester digester = createDigester();
316: digester.setDebug(host.getDebug());
317: digester.clear();
318: digester.push(this );
319: digester.parse(stream);
320: stream.close();
321: stream = null;
322: } catch (Exception e) {
323: host.log(
324: sm.getString("standardHost.installError", docBase),
325: e);
326: throw new IOException(e.toString());
327: } finally {
328: if (stream != null) {
329: try {
330: stream.close();
331: } catch (Throwable t) {
332: ;
333: }
334: }
335: }
336:
337: }
338:
339: /**
340: * Return the Context for the deployed application that is associated
341: * with the specified context path (if any); otherwise return
342: * <code>null</code>.
343: *
344: * @param contextPath The context path of the requested web application
345: */
346: public Context findDeployedApp(String contextPath) {
347:
348: return ((Context) host.findChild(contextPath));
349:
350: }
351:
352: /**
353: * Return the context paths of all deployed web applications in this
354: * Container. If there are no deployed applications, a zero-length
355: * array is returned.
356: */
357: public String[] findDeployedApps() {
358:
359: Container children[] = host.findChildren();
360: String results[] = new String[children.length];
361: for (int i = 0; i < children.length; i++)
362: results[i] = children[i].getName();
363: return (results);
364:
365: }
366:
367: /**
368: * Remove an existing web application, attached to the specified context
369: * path. If this application is successfully removed, a
370: * ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to all
371: * registered listeners, with the removed <code>Context</code> as
372: * an argument.
373: *
374: * @param contextPath The context path of the application to be removed
375: *
376: * @exception IllegalArgumentException if the specified context path
377: * is malformed (it must be "" or start with a slash)
378: * @exception IllegalArgumentException if the specified context path does
379: * not identify a currently installed web application
380: * @exception IOException if an input/output error occurs during
381: * removal
382: */
383: public void remove(String contextPath) throws IOException {
384:
385: // Validate the format and state of our arguments
386: if (contextPath == null)
387: throw new IllegalArgumentException(sm
388: .getString("standardHost.pathRequired"));
389: if (!contextPath.equals("") && !contextPath.startsWith("/"))
390: throw new IllegalArgumentException(sm.getString(
391: "standardHost.pathFormat", contextPath));
392:
393: // Locate the context and associated work directory
394: Context context = findDeployedApp(contextPath);
395: if (context == null)
396: throw new IllegalArgumentException(sm.getString(
397: "standardHost.pathMissing", contextPath));
398:
399: // Remove this web application
400: host.log(sm.getString("standardHost.removing", contextPath));
401: try {
402: host.removeChild(context);
403: host.fireContainerEvent(REMOVE_EVENT, context);
404: } catch (Exception e) {
405: host.log(sm.getString("standardHost.removeError",
406: contextPath), e);
407: throw new IOException(e.toString());
408: }
409:
410: }
411:
412: /**
413: * Start an existing web application, attached to the specified context
414: * path. Only starts a web application if it is not running.
415: *
416: * @param contextPath The context path of the application to be started
417: *
418: * @exception IllegalArgumentException if the specified context path
419: * is malformed (it must be "" or start with a slash)
420: * @exception IllegalArgumentException if the specified context path does
421: * not identify a currently installed web application
422: * @exception IOException if an input/output error occurs during
423: * startup
424: */
425: public void start(String contextPath) throws IOException {
426:
427: // Validate the format and state of our arguments
428: if (contextPath == null)
429: throw new IllegalArgumentException(sm
430: .getString("standardHost.pathRequired"));
431: if (!contextPath.equals("") && !contextPath.startsWith("/"))
432: throw new IllegalArgumentException(sm.getString(
433: "standardHost.pathFormat", contextPath));
434: Context context = findDeployedApp(contextPath);
435: if (context == null)
436: throw new IllegalArgumentException(sm.getString(
437: "standardHost.pathMissing", contextPath));
438: host.log("standardHost.start " + contextPath);
439: try {
440: ((Lifecycle) context).start();
441: } catch (LifecycleException e) {
442: host.log("standardHost.start " + contextPath + ": ", e);
443: throw new IllegalStateException("standardHost.start "
444: + contextPath + ": " + e);
445: }
446: }
447:
448: /**
449: * Stop an existing web application, attached to the specified context
450: * path. Only stops a web application if it is running.
451: *
452: * @param contextPath The context path of the application to be stopped
453: *
454: * @exception IllegalArgumentException if the specified context path
455: * is malformed (it must be "" or start with a slash)
456: * @exception IllegalArgumentException if the specified context path does
457: * not identify a currently installed web application
458: * @exception IOException if an input/output error occurs while stopping
459: * the web application
460: */
461: public void stop(String contextPath) throws IOException {
462:
463: // Validate the format and state of our arguments
464: if (contextPath == null)
465: throw new IllegalArgumentException(sm
466: .getString("standardHost.pathRequired"));
467: if (!contextPath.equals("") && !contextPath.startsWith("/"))
468: throw new IllegalArgumentException(sm.getString(
469: "standardHost.pathFormat", contextPath));
470: Context context = findDeployedApp(contextPath);
471: if (context == null)
472: throw new IllegalArgumentException(sm.getString(
473: "standardHost.pathMissing", contextPath));
474: host.log("standardHost.stop " + contextPath);
475: try {
476: ((Lifecycle) context).stop();
477: } catch (LifecycleException e) {
478: host.log("standardHost.stop " + contextPath + ": ", e);
479: throw new IllegalStateException("standardHost.stop "
480: + contextPath + ": " + e);
481: }
482:
483: }
484:
485: // ------------------------------------------------------ Delegated Methods
486:
487: /**
488: * Delegate a request to add a child Context to our associated Host.
489: *
490: * @param child The child Context to be added
491: */
492: public void addChild(Container child) {
493:
494: context = (Context) child;
495: String contextPath = context.getPath();
496: if (contextPath == null)
497: throw new IllegalArgumentException(sm
498: .getString("standardHost.pathRequired"));
499: else if (!contextPath.equals("")
500: && !contextPath.startsWith("/"))
501: throw new IllegalArgumentException(sm.getString(
502: "standardHost.pathFormat", contextPath));
503: if (host.findChild(contextPath) != null)
504: throw new IllegalStateException(sm.getString(
505: "standardHost.pathUsed", contextPath));
506: if (this .overrideDocBase != null)
507: context.setDocBase(this .overrideDocBase);
508: host.fireContainerEvent(PRE_INSTALL_EVENT, context);
509: host.addChild(child);
510: host.fireContainerEvent(INSTALL_EVENT, context);
511:
512: }
513:
514: /**
515: * Delegate a request for the parent class loader to our associated Host.
516: */
517: public ClassLoader getParentClassLoader() {
518:
519: return (host.getParentClassLoader());
520:
521: }
522:
523: // ------------------------------------------------------ Protected Methods
524:
525: /**
526: * Create (if necessary) and return a Digester configured to process the
527: * context configuration descriptor for an application.
528: */
529: protected Digester createDigester() {
530:
531: if (digester == null) {
532: digester = new Digester();
533: if (host.getDebug() > 0)
534: digester.setDebug(3);
535: digester.setValidating(false);
536: contextRuleSet = new ContextRuleSet("");
537: digester.addRuleSet(contextRuleSet);
538: namingRuleSet = new NamingRuleSet("Context/");
539: digester.addRuleSet(namingRuleSet);
540: }
541: return (digester);
542:
543: }
544:
545: }
|