View Javadoc

1   /*
2    * Copyright 1999,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  
18  package org.apache.catalina.manager;
19  
20  
21  import java.io.BufferedOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.FileNotFoundException;
26  import java.io.InputStream;
27  import java.io.IOException;
28  import java.io.PrintWriter;
29  import java.net.URL;
30  import java.net.MalformedURLException;
31  import java.net.URLConnection;
32  import java.util.Iterator;
33  import java.util.jar.JarEntry;
34  import java.util.jar.JarFile;
35  import javax.naming.Binding;
36  import javax.naming.InitialContext;
37  import javax.naming.NamingEnumeration;
38  import javax.naming.NamingException;
39  import javax.naming.directory.DirContext;
40  import javax.servlet.ServletException;
41  import javax.servlet.ServletInputStream;
42  import javax.servlet.UnavailableException;
43  import javax.servlet.http.HttpServlet;
44  import javax.servlet.http.HttpServletRequest;
45  import javax.servlet.http.HttpServletResponse;
46  import org.apache.catalina.Container;
47  import org.apache.catalina.ContainerServlet;
48  import org.apache.catalina.Context;
49  import org.apache.catalina.Deployer;
50  import org.apache.catalina.Engine;
51  import org.apache.catalina.Globals;
52  import org.apache.catalina.Host;
53  import org.apache.catalina.Role;
54  import org.apache.catalina.Server;
55  import org.apache.catalina.ServerFactory;
56  import org.apache.catalina.Session;
57  import org.apache.catalina.UserDatabase;
58  import org.apache.catalina.Wrapper;
59  import org.apache.catalina.core.StandardServer;
60  import org.apache.catalina.util.ServerInfo;
61  import org.apache.catalina.util.StringManager;
62  import org.apache.naming.resources.ProxyDirContext;
63  import org.apache.naming.resources.WARDirContext;
64  
65  import org.talika.tms.Constants;
66  
67  
68  /**
69   * Servlet that enables remote management of the web applications installed
70   * within the same virtual host as this web application is.  Normally, this
71   * functionality will be protected by a security constraint in the web
72   * application deployment descriptor.  However, this requirement can be
73   * relaxed during testing.
74   * <p>
75   * This servlet examines the value returned by <code>getPathInfo()</code>
76   * and related query parameters to determine what action is being requested.
77   * The following actions and parameters (starting after the servlet path)
78   * are supported:
79   * <ul>
80   * <li><b>/deploy?config={config-url}</b> - Install and start a new
81   *     web application, based on the contents of the context configuration
82   *     file found at the specified URL.  The <code>docBase</code> attribute
83   *     of the context configuration file is used to locate the actual
84   *     WAR or directory containing the application.</li>
85   * <li><b>/deploy?config={config-url}&war={war-url}/</b> - Install and start
86   *     a new web application, based on the contents of the context
87   *     configuration file found at <code>{config-url}</code>, overriding the
88   *     <code>docBase</code> attribute with the contents of the web
89   *     application archive found at <code>{war-url}</code>.</li>
90   * <li><b>/deploy?path=/xxx&war={war-url}</b> - Install and start a new
91   *     web application attached to context path <code>/xxx</code>, based
92   *     on the contents of the web application archive found at the
93   *     specified URL.</li>
94   * <li><b>/list</b> - List the context paths of all currently installed web
95   *     applications for this virtual host.  Each context will be listed with
96   *     the following format <code>path:status:sessions</code>.
97   *     Where path is the context path.  Status is either running or stopped.
98   *     Sessions is the number of active Sessions.</li>
99   * <li><b>/reload?path=/xxx</b> - Reload the Java classes and resources for
100  *     the application at the specified path.</li>
101  * <li><b>/resources?type=xxxx</b> - Enumerate the available global JNDI
102  *     resources, optionally limited to those of the specified type
103  *     (fully qualified Java class name), if available.</li>
104  * <li><b>/roles</b> - Enumerate the available security role names and
105  *     descriptions from the user database connected to the <code>users</code>
106  *     resource reference.
107  * <li><b>/serverinfo</b> - Display system OS and JVM properties.
108  * <li><b>/sessions?path=/xxx</b> - List session information about the web
109  *     application attached to context path <code>/xxx</code> for this
110  *     virtual host.</li>
111  * <li><b>/start?path=/xxx</b> - Start the web application attached to
112  *     context path <code>/xxx</code> for this virtual host.</li>
113  * <li><b>/stop?path=/xxx</b> - Stop the web application attached to
114  *     context path <code>/xxx</code> for this virtual host.</li>
115  * <li><b>/undeploy?path=/xxx</b> - Shutdown and remove the web application
116  *     attached to context path <code>/xxx</code> for this virtual host,
117  *     and remove the underlying WAR file or document base directory.
118  *     (<em>NOTE</em> - This is only allowed if the WAR file or document
119  *     base is stored in the <code>appBase</code> directory of this host,
120  *     typically as a result of being placed there via the <code>/deploy</code>
121  *     command.</li>
122  * </ul>
123  * <p>Use <code>path=/</code> for the ROOT context.</p>
124  * <p>The syntax of the URL for a web application archive must conform to one
125  * of the following patterns to be successfully deployed:</p>
126  * <ul>
127  * <li><b>file:/absolute/path/to/a/directory</b> - You can specify the absolute
128  *     path of a directory that contains the unpacked version of a web
129  *     application.  This directory will be attached to the context path you
130  *     specify without any changes.</li>
131  * <li><b>jar:file:/absolute/path/to/a/warfile.war!/</b> - You can specify a
132  *     URL to a local web application archive file.  The syntax must conform to
133  *     the rules specified by the <code>JarURLConnection</code> class for a
134  *     reference to an entire JAR file.</li>
135  * <li><b>jar:http://hostname:port/path/to/a/warfile.war!/</b> - You can specify
136  *     a URL to a remote (HTTP-accessible) web application archive file.  The
137  *     syntax must conform to the rules specified by the
138  *     <code>JarURLConnection</code> class for a reference to an entire
139  *     JAR file.</li>
140  * </ul>
141  * <p>
142  * <b>NOTE</b> - Attempting to reload or remove the application containing
143  * this servlet itself will not succeed.  Therefore, this servlet should
144  * generally be deployed as a separate web application within the virtual host
145  * to be managed.
146  * <p>
147  * <b>NOTE</b> - For security reasons, this application will not operate
148  * when accessed via the invoker servlet.  You must explicitly map this servlet
149  * with a servlet mapping, and you will always want to protect it with
150  * appropriate security constraints as well.
151  * <p>
152  * The following servlet initialization parameters are recognized:
153  * <ul>
154  * <li><b>debug</b> - The debugging detail level that controls the amount
155  *     of information that is logged by this servlet.  Default is zero.
156  * </ul>
157  *
158  * @author Craig R. McClanahan
159  * @author Remy Maucherat
160  * @author Jose M. Palomar
161  * @version $Revision: 214 $ $Date: 2004-10-24 12:51:42 +0200 (dom 24 de oct de 2004) $
162  * 
163  * @todo Test more deploy functions over multiple hosts.
164  */
165 
166 public class ManagerXServlet
167     extends HttpServlet implements ContainerServlet {
168 
169 
170     // ----------------------------------------------------- Instance Variables
171 
172 
173     /**
174      * Path where context descriptors should be deployed.
175      */
176     protected File configBase = null;
177 
178 
179     /**
180      * The Context container associated with our web application.
181      */
182     protected Context context = null;
183 
184 
185     /**
186      * The debugging detail level for this servlet.
187      */
188     protected int debug = 1;
189 
190 
191     /**
192      * Path used to store revisions of webapps.
193      */
194     protected File versioned = null;
195 
196 
197     /**
198      * Path used to store context descriptors.
199      */
200     protected File contextDescriptors = null;
201 
202 
203     /**
204      * The Engine container of all our vhosts
205      */
206     protected Engine engine = null;
207 
208 
209     /**
210      * The global JNDI <code>NamingContext</code> for this server,
211      * if available.
212      */
213     protected javax.naming.Context global = null;
214 
215 
216     /**
217      * The string manager for this package.
218      */
219     protected static StringManager sm =
220         StringManager.getManager(Constants.Package);
221 
222 
223     /**
224      * The Wrapper container associated with this servlet.
225      */
226     protected Wrapper wrapper = null;
227 
228 
229     // ----------------------------------------------- ContainerServlet Methods
230 
231 
232     /**
233      * Return the Wrapper with which we are associated.
234      */
235     public Wrapper getWrapper() {
236 
237         return (this.wrapper);
238 
239     }
240 
241 
242     /**
243      * Set the Wrapper with which we are associated.
244      *
245      * @param wrapper The new wrapper
246      */
247     public void setWrapper(Wrapper wrapper) {
248 
249         this.wrapper = wrapper;
250         if (wrapper == null) {
251             context = null;
252             engine = null;
253         } else {
254             context = (Context) wrapper.getParent();
255             engine = (Engine) context.getParent().getParent();
256         }
257 
258     }
259 
260 
261     // --------------------------------------------------------- Public Methods
262 
263 
264     /**
265      * Finalize this servlet.
266      */
267     public void destroy() {
268 
269         ;       // No actions necessary
270 
271     }
272 
273 
274     /**
275      * Process a GET request for the specified resource.
276      *
277      * @param request The servlet request we are processing
278      * @param response The servlet response we are creating
279      *
280      * @exception IOException if an input/output error occurs
281      * @exception ServletException if a servlet-specified error occurs
282      */
283     public void doGet(HttpServletRequest request,
284                       HttpServletResponse response)
285         throws IOException, ServletException {
286 
287         // Verify that we were not accessed using the invoker servlet
288         if (request.getAttribute(Globals.INVOKED_ATTR) != null)
289             throw new UnavailableException
290                 (sm.getString("managerServlet.cannotInvoke"));
291 
292         // Identify the request parameters that we need
293         String command = request.getPathInfo();
294         if (command == null)
295             command = request.getServletPath();
296         String vhost = request.getParameter("vhost");
297         String config = request.getParameter("config");
298         String path = request.getParameter("path");
299         String type = request.getParameter("type");
300         String war = request.getParameter("war");
301         String tag = request.getParameter("tag");
302         boolean update = false;
303         if ((request.getParameter("update") != null) 
304             && (request.getParameter("update").equals("true"))) {
305             update = true;
306         }
307 
308         // Prepare our output writer to generate the response message
309         response.setContentType("text/plain; charset=" + Constants.CHARSET);
310         PrintWriter writer = response.getWriter();
311 
312         // Process the requested command (note - "/deploy" is not listed here)
313         if (command == null) {
314             writer.println(sm.getString("managerServlet.noCommand"));
315         } else if (command.equals("/deploy")) {
316             if (war != null) {
317                 deploy(writer, vhost, config, path, war, update);
318             } else {
319                 deploy(writer, vhost, path, tag);
320             }
321         } else if (command.equals("/install")) {
322             // Deprecated
323             deploy(writer, vhost, config, path, war, false);
324         } else if (command.equals("/vhosts")) {
325             vhosts(writer);
326         } else if (command.equals("/list")) {
327             list(writer, vhost);
328         } else if (command.equals("/reload")) {
329             reload(writer, vhost, path);
330         } else if (command.equals("/remove")) {
331             // Deprecated
332             remove(writer, vhost, path);
333         } else if (command.equals("/resources")) {
334             resources(writer, type);
335         } else if (command.equals("/roles")) {
336             roles(writer);
337         } else if (command.equals("/save")) {
338             save(writer, vhost, path);
339         } else if (command.equals("/serverinfo")) {
340             serverinfo(writer);
341         } else if (command.equals("/sessions")) {
342             sessions(writer, vhost, path);
343         } else if (command.equals("/start")) {
344             start(writer, vhost, path);
345         } else if (command.equals("/stop")) {
346             stop(writer, vhost, path);
347         } else if (command.equals("/undeploy")) {
348             undeploy(writer, vhost, path);
349         } else {
350             writer.println(sm.getString("managerServlet.unknownCommand",
351                                         command));
352         }
353 
354         // Finish up the response
355         writer.flush();
356         writer.close();
357 
358     }
359 
360 
361     /**
362      * Process a PUT request for the specified resource.
363      *
364      * @param request The servlet request we are processing
365      * @param response The servlet response we are creating
366      *
367      * @exception IOException if an input/output error occurs
368      * @exception ServletException if a servlet-specified error occurs
369      */
370     public void doPut(HttpServletRequest request,
371                       HttpServletResponse response)
372         throws IOException, ServletException {
373 
374         // Verify that we were not accessed using the invoker servlet
375         if (request.getAttribute(Globals.INVOKED_ATTR) != null)
376             throw new UnavailableException
377                 (sm.getString("managerServlet.cannotInvoke"));
378 
379         // Identify the request parameters that we need
380         String command = request.getPathInfo();
381         if (command == null)
382             command = request.getServletPath();
383         String vhost = request.getParameter("vhost");
384         String path = request.getParameter("path");
385         String tag = request.getParameter("tag");
386         boolean update = false;
387         if ((request.getParameter("update") != null) 
388             && (request.getParameter("update").equals("true"))) {
389             update = true;
390         }
391 
392         // Prepare our output writer to generate the response message
393         response.setContentType("text/plain;charset="+Constants.CHARSET);
394         PrintWriter writer = response.getWriter();
395 
396         // Process the requested command
397         if (command == null) {
398             writer.println(sm.getString("managerServlet.noCommand"));
399         } else if (command.equals("/deploy")) {
400             deploy(writer, vhost, path, tag, update, request);
401         } else {
402             writer.println(sm.getString("managerServlet.unknownCommand",
403                                         command));
404         }
405 
406         // Finish up the response
407         writer.flush();
408         writer.close();
409 
410     }
411 
412 
413     /**
414      * Initialize this servlet.
415      */
416     public void init() throws ServletException {
417 
418         // Ensure that our ContainerServlet properties have been set
419         if ((wrapper == null) || (context == null))
420             throw new UnavailableException
421                 (sm.getString("managerServlet.noWrapper"));
422 
423         // Verify that we were not accessed using the invoker servlet
424         String servletName = getServletConfig().getServletName();
425         if (servletName == null)
426             servletName = "";
427         if (servletName.startsWith("org.apache.catalina.INVOKER."))
428             throw new UnavailableException
429                 (sm.getString("managerServlet.cannotInvoke"));
430 
431         // Set our properties from the initialization parameters
432         String value = null;
433         try {
434             value = getServletConfig().getInitParameter("debug");
435             debug = Integer.parseInt(value);
436         } catch (Throwable t) {
437             ;
438         }
439 
440         // Acquire global JNDI resources if available
441         Server server = ServerFactory.getServer();
442         if ((server != null) && (server instanceof StandardServer)) {
443             global = ((StandardServer) server).getGlobalNamingContext();
444         }
445 
446         // Calculate the directory into which we will be deploying applications
447         versioned = (File) getServletContext().getAttribute
448             ("javax.servlet.context.tempdir");
449 
450         configBase = new File(System.getProperty("catalina.base"), "conf");
451         Container container = context;
452         Container host = null;
453         Container engine = null;
454         while (container != null) {
455             if (container instanceof Host)
456                 host = container;
457             if (container instanceof Engine)
458                 engine = container;
459             container = container.getParent();
460         }
461         if (engine != null) {
462             configBase = new File(configBase, engine.getName());
463         }
464         if (host != null) {
465             configBase = new File(configBase, host.getName());
466         }
467         // Note: The directory must exist for this to work.
468 
469         // Log debugging messages as necessary
470         if (debug >= 1) {
471             log("init: Associated with Engine '" +
472                 engine.getName() + "'");
473             if (global != null) {
474                 log("init: Global resources are available");
475             }
476         }
477 
478     }
479 
480 
481 
482     // -------------------------------------------------------- Private Methods
483 
484 
485     /**
486      * Render a list of the currently active vhosts.
487      *
488      * @param writer Writer to render to
489      */
490     protected void vhosts(PrintWriter writer) {
491 
492         if (debug >= 1)
493             log("list: Listing virtual hosts for engine '" +
494                 engine.getName() + "'");
495 
496         writer.println(sm.getString("managerServlet.vhosts",
497                                     engine.getName()));
498         
499         Container hosts[] = engine.findChildren();
500         for (int i = 0; i < hosts.length; i++) {            
501             writer.println(sm.getString("managerServlet.hostitem",
502                                         hosts[i].getName()));            
503         }
504         
505     }
506     
507     /**
508      * Store server configuration.
509      * 
510      * @param vhost Virtual host to be listed
511      * @param path Optional context path to save
512      */
513     protected synchronized void save(PrintWriter writer, String vhost, String path) {
514 
515         Server server = ServerFactory.getServer();
516 
517         if (!(server instanceof StandardServer)) {
518             writer.println(sm.getString("managerServlet.saveFail", server));
519             return;
520         }
521 
522         if(vhost == null || vhost.length() == 0) {
523             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
524             return;
525         }        
526 
527         Deployer deployer = (Deployer) engine.findChild(vhost);
528         if (deployer == null) {
529             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
530             return;                
531         }
532 
533         if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
534             try {
535                 ((StandardServer) server).storeConfig();
536                 writer.println(sm.getString("managerServlet.saved"));
537             } catch (Exception e) {
538                 log("managerServlet.storeConfig", e);
539                 writer.println(sm.getString("managerServlet.exception",
540                                             e.toString()));
541                 return;
542             }
543         } else {
544             String contextPath = path;
545             if (path.equals("/")) {
546                 contextPath = "";
547             }
548             Context context =  deployer.findDeployedApp(contextPath);
549             if (context == null) {
550                 writer.println(sm.getString("managerServlet.noContext", path));
551                 return;
552             }
553             try {
554                 ((StandardServer) server).storeContext(context);
555                 writer.println(sm.getString("managerServlet.savedContext", 
556                                path));
557             } catch (Exception e) {
558                 log("managerServlet.save[" + path + "]", e);
559                 writer.println(sm.getString("managerServlet.exception",
560                                             e.toString()));
561                 return;
562             }
563         }
564 
565     }
566 
567 
568     /**
569      * Deploy a web application archive (included in the current request)
570      * at the specified context path.
571      *
572      * @param writer Writer to render results to
573      * @param vhost Virtual host to be listed
574      * @param path Context path of the application to be installed
575      * @param tag Tag to be associated with the webapp
576      * @param request Servlet request we are processing
577      */
578     protected synchronized void deploy
579         (PrintWriter writer, String vhost, String path,
580          String tag, boolean update, HttpServletRequest request) {
581 
582         if (debug >= 1) {
583             log("deploy: Deploying web application at '" + vhost + ":" + path + "'");
584         }
585 
586         if(vhost == null || vhost.length() == 0) {
587             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
588             return;
589         }        
590 
591         Deployer deployer = (Deployer) engine.findChild(vhost);
592         if (deployer == null) {
593             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
594             return;                
595         }
596 
597         // Validate the requested context path
598         if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
599             writer.println(sm.getString("managerServlet.invalidPath", path));
600             return;
601         }
602         String displayPath = path;
603         if( path.equals("/") )
604             path = "";
605         String basename = getConfigFile(path);
606 
607         // Check if app already exists, or undeploy it if updating
608         Context context =  deployer.findDeployedApp(path);
609         if (update) {
610             if (context != null) {
611                 undeploy(writer, vhost, displayPath);
612             }
613             context =  deployer.findDeployedApp(path);
614         }
615         if (context != null) {
616             writer.println
617                 (sm.getString("managerServlet.alreadyContext",
618                               displayPath));
619             return;
620         }
621 
622         // Calculate the base path
623         File deployedPath = getDeployed(context);
624         if (tag != null) {
625             deployedPath = new File(versioned, tag);
626             deployedPath.mkdirs();
627         }
628 
629         // Upload the web application archive to a local WAR file
630         File localWar = new File(deployedPath, basename + ".war");
631         if (debug >= 2) {
632             log("Uploading WAR file to " + localWar);
633         }
634         try {
635             uploadWar(request, localWar);
636         } catch (IOException e) {
637             log("managerServlet.upload[" + displayPath + "]", e);
638             writer.println(sm.getString("managerServlet.exception",
639                                         e.toString()));
640             return;
641         }
642 
643         // Copy WAR and XML to the host base
644         if (tag != null) {
645             deployedPath = getDeployed(context);
646             File localWarCopy = new File(deployedPath, basename + ".war");
647             copy(localWar, localWarCopy);
648             localWar = localWarCopy;
649         }
650 
651         String war = null;
652         try {
653             URL url = localWar.toURL();
654             war = url.toString();
655             war = "jar:" + war + "!/";
656         } catch(MalformedURLException e) {
657             log("managerServlet.badUrl[" + displayPath + "]", e);
658             writer.println(sm.getString("managerServlet.exception",
659                                         e.toString()));
660             return;
661         }
662 
663         // Extract the nested context deployment file (if any)
664         File localXml = new File(configBase, basename + ".xml");
665         if (debug >= 2) {
666             log("Extracting XML file to " + localXml);
667         }
668         try {
669             extractXml(localWar, localXml);
670         } catch (IOException e) {
671             log("managerServlet.extract[" + displayPath + "]", e);
672             writer.println(sm.getString("managerServlet.exception",
673                                         e.toString()));
674             return;
675         }
676         String config = null;
677         try {
678             if (localXml.exists()) {
679                 URL url = localXml.toURL();
680                 config = url.toString();
681             }
682         } catch (MalformedURLException e) {
683             log("managerServlet.badUrl[" + displayPath + "]", e);
684             writer.println(sm.getString("managerServlet.exception",
685                                         e.toString()));
686             return;
687         }
688 
689         // Deploy this web application
690         deploy(writer, vhost, config, path, war, update);
691 
692     }
693 
694 
695     /**
696      * Install an application for the specified path from the specified
697      * web application archive.
698      *
699      * @param writer Writer to render results to
700      * @param vhost Virtual host to be listed
701      * @param tag Revision tag to deploy from
702      * @param path Context path of the application to be installed
703      */
704     protected void deploy(PrintWriter writer, String vhost, String path, String tag) {
705 
706         if(vhost == null || vhost.length() == 0) {
707             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
708             return;
709         }        
710 
711         Deployer deployer = (Deployer) engine.findChild(vhost);
712         if (deployer == null) {
713             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
714             return;                
715         }
716 
717         // Validate the requested context path
718         if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
719             writer.println(sm.getString("managerServlet.invalidPath", path));
720             return;
721         }
722         String displayPath = path;
723         if( path.equals("/") )
724             path = "";
725         String basename = getConfigFile(path);
726 
727         // Calculate the base path
728         File deployedPath = versioned;
729         if (tag != null) {
730             deployedPath = new File(deployedPath, tag);
731         }
732 
733         // Find the local WAR file
734         File localWar = new File(deployedPath, basename + ".war");
735         // Find the local context deployment file (if any)
736         File localXml = new File(configBase, basename + ".xml");
737 
738         // Check if app already exists, or undeploy it if updating
739         Context context =  deployer.findDeployedApp(path);
740         if (context != null) {
741             undeploy(writer, vhost, displayPath);
742         }
743 
744         // Copy WAR and XML to the host base
745         if (tag != null) {
746             File deployed = getDeployed(context);
747             File localWarCopy = new File(deployed, basename + ".war");
748             copy(localWar, localWarCopy);
749             try {
750                 extractXml(localWar, localXml);
751             } catch (IOException e) {
752                 log("managerServlet.extract[" + displayPath + "]", e);
753                 writer.println(sm.getString("managerServlet.exception",
754                                             e.toString()));
755                 return;
756             }
757             localWar = localWarCopy;
758         }
759 
760         // Compute URLs
761         String war = null;
762         try {
763             URL url = localWar.toURL();
764             war = url.toString();
765             war = "jar:" + war + "!/";
766         } catch(MalformedURLException e) {
767             log("managerServlet.badUrl[" + displayPath + "]", e);
768             writer.println(sm.getString("managerServlet.exception",
769                                         e.toString()));
770             return;
771         }
772         String config = null;
773         try {
774             if (localXml.exists()) {
775                 URL url = localXml.toURL();
776                 config = url.toString();
777             }
778         } catch (MalformedURLException e) {
779             log("managerServlet.badUrl[" + displayPath + "]", e);
780             writer.println(sm.getString("managerServlet.exception",
781                                         e.toString()));
782             return;
783         }
784 
785         // Deploy webapp
786         deploy(writer, vhost, config, path, war, false);
787 
788     }
789 
790 
791     /**
792      * Install an application for the specified path from the specified
793      * web application archive.
794      *
795      * @param writer Writer to render results to
796      * @param vhost Virtual host to be listed
797      * @param config URL of the context configuration file to be installed
798      * @param path Context path of the application to be installed
799      * @param war URL of the web application archive to be installed
800      * @param update true to override any existing webapp on the path
801      */
802     protected void deploy(PrintWriter writer, String vhost, String config,
803                           String path, String war, boolean update) {
804 
805         if(vhost == null || vhost.length() == 0) {
806             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
807             return;
808         }        
809 
810         Deployer deployer = (Deployer) engine.findChild(vhost);
811         if (deployer == null) {
812             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
813             return;                
814         }
815 
816         if (war != null && war.length() == 0) {
817             war = null;
818         }
819 
820         if (debug >= 1) {
821             if (config != null && config.length() > 0) {
822                 if (war != null) {
823                     log("install: Installing context configuration at '" +
824                         config + "' from '" + war + "'");
825                 } else {
826                     log("install: Installing context configuration at '" +
827                         config + "'");
828                 }
829             } else {
830                 if (path != null && path.length() > 0) {
831                     log("install: Installing web application at '" + path +
832                         "' from '" + war + "'");
833                 } else {
834                     log("install: Installing web application from '" + war + "'");
835                 }
836             }
837         }
838 
839         // See if directory/war is relative to host appBase
840         if (war != null && war.indexOf('/') < 0 ) {
841             // Identify the appBase of the owning Host of this Context (if any)
842             String appBase = null;
843             File appBaseDir = null;
844             if (context.getParent() instanceof Host) {
845                 appBase = ((Host) context.getParent()).getAppBase();
846                 appBaseDir = new File(appBase);
847                 if (!appBaseDir.isAbsolute()) {
848                     appBaseDir = new File(System.getProperty("catalina.base"),
849                                           appBase);
850                 }
851                 File file = new File(appBaseDir, war);
852                 try {
853                     URL url = file.toURL();
854                     war = url.toString();
855                     if (war.toLowerCase().endsWith(".war")) {
856                         war = "jar:" + war + "!/";
857                     }
858                 } catch(MalformedURLException e) {
859                     ;
860                 }
861             }
862         }
863 
864         if (config != null && config.length() > 0) {
865 
866             if ((war != null) &&
867                 (!war.startsWith("file:") && !war.startsWith("jar:"))) {
868                 writer.println(sm.getString("managerServlet.invalidWar", war));
869                 return;
870             }
871 
872             try {
873                 if (war == null) {
874                     deployer.install(new URL(config), null);
875                 } else {
876                     deployer.install(new URL(config), new URL(war));
877                 }
878                 writer.println(sm.getString("managerServlet.configured",
879                                             config));
880             } catch (Throwable t) {
881                 log("ManagerServlet.configure[" + config + "]", t);
882                 writer.println(sm.getString("managerServlet.exception",
883                                             t.toString()));
884                 return;
885             }
886 
887         } else {
888 
889             if ((war == null) ||
890                 (!war.startsWith("file:") && !war.startsWith("jar:"))) {
891                 writer.println(sm.getString("managerServlet.invalidWar", war));
892                 return;
893             }
894 
895             if (path == null || path.length() == 0) {
896                 if (deployer.isDeployXML()) {
897                     // Use embedded META-INF/context.xml if present
898                     URL contextXml = null;
899                     InputStream stream = null;
900                     try {
901                         String contextWar = war;
902                         if (war.startsWith("file:")) {
903                             if (war.endsWith(".war")) {
904                                 contextWar = "jar:" + war + "!/";
905                             } else {
906                                 contextWar = war + '/';
907                             }
908                         }
909                         contextXml = new URL(contextWar +
910                                              "META-INF/context.xml");
911                         URLConnection jarUrlConnection=contextXml.openConnection();
912                         jarUrlConnection.setUseCaches(false);
913                         stream = jarUrlConnection.getInputStream();;
914 
915                         // WAR contains META-INF/context.xml resource - install
916                         deployer.install(new URL(contextWar));
917                         return;
918                     } catch (FileNotFoundException fnfe) {
919 			// No META-INF/context.xml resource - keep going
920                     } catch (Throwable t) {
921                         log("ManagerServlet.configure[" + contextXml + "]", t);
922                         writer.println(sm.getString("managerServlet.exception",
923                                                     t.toString()));
924                         return;
925                     } finally {
926                         if (stream != null) {
927                             try {
928                                 stream.close();
929                             } catch (Throwable t) {
930                                 // do nothing
931                             }
932                         }
933 
934                     }
935                 }
936 
937                 int end = war.length();
938                 String filename = war.toLowerCase();
939                 if (filename.endsWith("!/")) {
940                     filename = filename.substring(0,filename.length()-2);
941                     end -= 2;
942                 }
943                 if (filename.endsWith(".war")) {
944                     filename = filename.substring(0,filename.length()-4);
945                     end -= 4;
946                 }
947                 if (filename.endsWith("/")) {
948                     filename = filename.substring(0,filename.length()-1);
949                     end--;
950                 }
951                 int beg = filename.lastIndexOf('/') + 1;
952                 if (beg < 0 || end < 0 || beg >= end) {
953                     writer.println(sm.getString("managerServlet.invalidWar", war));
954                     return;
955                 }
956                 path = "/" + war.substring(beg, end);
957                 if (path.equals("/ROOT")) {
958                     path = "/";
959                 }
960             }
961 
962             if (path == null || path.length() == 0 || !path.startsWith("/")) {
963                 writer.println(sm.getString("managerServlet.invalidPath",
964                                             path));
965                 return;
966             }
967             String displayPath = path;
968             if("/".equals(path)) {
969                 path = "";
970             }
971 
972             // Check if app already exists, or undeploy it if updating
973             Context context =  deployer.findDeployedApp(path);
974             if (update) {
975                 if (context != null) {
976                     undeploy(writer, vhost, displayPath);
977                 }
978                 context =  deployer.findDeployedApp(path);
979             }
980             if (context != null) {
981                 writer.println
982                     (sm.getString("managerServlet.alreadyContext",
983                                   displayPath));
984                 return;
985             }
986 
987             try {
988                 deployer.install(path, new URL(war));
989                 writer.println(sm.getString("managerServlet.deployed",
990                                             displayPath));
991             } catch (Throwable t) {
992                 log("ManagerServlet.install[" + displayPath + "]", t);
993                 writer.println(sm.getString("managerServlet.exception",
994                                             t.toString()));
995             }
996 
997         }
998 
999     }
1000 
1001 
1002     /**
1003      * Render a list of the currently active Contexts in our virtual host.
1004      *
1005      * @param writer Writer to render to
1006      * @param vhost Virtual host to be listed
1007      */
1008     protected void list(PrintWriter writer, String vhost) {
1009 
1010         if (debug >= 1)
1011             log("list: Listing contexts for virtual host '" +
1012                 vhost + "'");
1013 
1014         if(vhost == null || vhost.length() == 0) {
1015             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1016             return;
1017         }        
1018 
1019         Deployer deployer = (Deployer) engine.findChild(vhost);
1020         if (deployer == null) {
1021             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1022             return;                
1023         }
1024 
1025         writer.println(sm.getString("managerServlet.listed",
1026                                     deployer.getName()));
1027         String contextPaths[] = deployer.findDeployedApps();
1028         for (int i = 0; i < contextPaths.length; i++) {
1029             Context context = deployer.findDeployedApp(contextPaths[i]);
1030             String displayPath = contextPaths[i];
1031             if( displayPath.equals("") )
1032                 displayPath = "/";
1033             if (context != null ) {
1034                 if (context.getAvailable()) {
1035                     writer.println(sm.getString("managerServlet.listitem",
1036                                                 displayPath,
1037                                                 "running",
1038                                       "" + context.getManager().findSessions().length,
1039                                                 context.getDocBase()));
1040                 } else {
1041                     writer.println(sm.getString("managerServlet.listitem",
1042                                                 displayPath,
1043                                                 "stopped",
1044                                                 "0",
1045                                                 context.getDocBase()));
1046                 }
1047             }
1048         }
1049     }
1050 
1051 
1052     /**
1053      * Reload the web application at the specified context path.
1054      *
1055      * @param writer Writer to render to
1056      * @param vhost Virtual host to be listed
1057      * @param path Context path of the application to be restarted
1058      */
1059     protected void reload(PrintWriter writer, String vhost, String path) {
1060 
1061         if (debug >= 1)
1062             log("restart: Reloading web application at '" + vhost + ":" + path + "'");
1063 
1064         if(vhost == null || vhost.length() == 0) {
1065             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1066             return;
1067         }        
1068 
1069         Deployer deployer = (Deployer) engine.findChild(vhost);
1070         if (deployer == null) {
1071             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1072             return;                
1073         }
1074 
1075         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1076             writer.println(sm.getString("managerServlet.invalidPath", path));
1077             return;
1078         }
1079         String displayPath = path;
1080         if( path.equals("/") )
1081             path = "";
1082 
1083         try {
1084             Context context = deployer.findDeployedApp(path);
1085             if (context == null) {
1086                 writer.println(sm.getString
1087                                ("managerServlet.noContext", displayPath));
1088                 return;
1089             }
1090             DirContext resources = context.getResources();
1091             if (resources instanceof ProxyDirContext) {
1092                 resources = ((ProxyDirContext) resources).getDirContext();
1093             }
1094             if (resources instanceof WARDirContext) {
1095                 writer.println(sm.getString
1096                                ("managerServlet.noReload", displayPath));
1097                 return;
1098             }
1099             // It isn't possible for the manager to reload itself
1100             if (context.getPath().equals(this.context.getPath())) {
1101                 writer.println(sm.getString("managerServlet.noSelf"));
1102                 return;
1103             }
1104             context.reload();
1105             writer.println
1106                 (sm.getString("managerServlet.reloaded", displayPath));
1107         } catch (Throwable t) {
1108             log("ManagerServlet.reload[" + displayPath + "]", t);
1109             writer.println(sm.getString("managerServlet.exception",
1110                                         t.toString()));
1111         }
1112 
1113     }
1114 
1115 
1116     /**
1117      * Remove the web application at the specified context path.
1118      *
1119      * @param writer Writer to render to
1120      * @param vhost Virtual host to be listed
1121      * @param path Context path of the application to be removed
1122      * @deprecated Replaced by undeploy
1123      */
1124     protected void remove(PrintWriter writer, String vhost, String path) {
1125 
1126         if (debug >= 1)
1127             log("remove: Removing web application at '" + vhost + ":" + path + "'");
1128 
1129         if(vhost == null || vhost.length() == 0) {
1130             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1131             return;
1132         }        
1133 
1134         Deployer deployer = (Deployer) engine.findChild(vhost);
1135         if (deployer == null) {
1136             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1137             return;                
1138         }
1139 
1140         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1141             writer.println(sm.getString("managerServlet.invalidPath", path));
1142             return;
1143         }
1144         String displayPath = path;
1145         if( path.equals("/") )
1146             path = "";
1147 
1148         try {
1149             Context context = deployer.findDeployedApp(path);
1150             if (context == null) {
1151                 writer.println(sm.getString("managerServlet.noContext", displayPath));
1152                 return;
1153             }
1154             // It isn't possible for the manager to remove itself
1155             if (context.getPath().equals(this.context.getPath())) {
1156                 writer.println(sm.getString("managerServlet.noSelf"));
1157                 return;
1158             }
1159             deployer.remove(path,true);
1160             writer.println(sm.getString("managerServlet.undeployed", displayPath));
1161         } catch (Throwable t) {
1162             log("ManagerServlet.remove[" + displayPath + "]", t);
1163             writer.println(sm.getString("managerServlet.exception",
1164                                         t.toString()));
1165         }
1166 
1167     }
1168 
1169 
1170     /**
1171      * Render a list of available global JNDI resources.
1172      *
1173      * @param type Fully qualified class name of the resource type of interest,
1174      *  or <code>null</code> to list resources of all types
1175      */
1176     protected void resources(PrintWriter writer, String type) {
1177 
1178         if (debug >= 1) {
1179             if (type != null) {
1180                 log("resources:  Listing resources of type " + type);
1181             } else {
1182                 log("resources:  Listing resources of all types");
1183             }
1184         }
1185 
1186         // Is the global JNDI resources context available?
1187         if (global == null) {
1188             writer.println(sm.getString("managerServlet.noGlobal"));
1189             return;
1190         }
1191 
1192         // Enumerate the global JNDI resources of the requested type
1193         if (type != null) {
1194             writer.println(sm.getString("managerServlet.resourcesType",
1195                                         type));
1196         } else {
1197             writer.println(sm.getString("managerServlet.resourcesAll"));
1198         }
1199 
1200         Class clazz = null;
1201         try {
1202             if (type != null) {
1203                 clazz = Class.forName(type);
1204             }
1205         } catch (Throwable t) {
1206             log("ManagerServlet.resources[" + type + "]", t);
1207             writer.println(sm.getString("managerServlet.exception",
1208                                         t.toString()));
1209             return;
1210         }
1211 
1212         printResources(writer, "", global, type, clazz);
1213 
1214     }
1215 
1216 
1217     /**
1218      * List the resources of the given context.
1219      */
1220     protected void printResources(PrintWriter writer, String prefix,
1221                                   javax.naming.Context namingContext,
1222                                   String type, Class clazz) {
1223 
1224         try {
1225             NamingEnumeration items = namingContext.listBindings("");
1226             while (items.hasMore()) {
1227                 Binding item = (Binding) items.next();
1228                 if (item.getObject() instanceof javax.naming.Context) {
1229                     printResources
1230                         (writer, prefix + item.getName() + "/",
1231                          (javax.naming.Context) item.getObject(), type, clazz);
1232                 } else {
1233                     if ((clazz != null) &&
1234                         (!(clazz.isInstance(item.getObject())))) {
1235                         continue;
1236                     }
1237                     writer.print(prefix + item.getName());
1238                     writer.print(':');
1239                     writer.print(item.getClassName());
1240                     // Do we want a description if available?
1241                     writer.println();
1242                 }
1243             }
1244         } catch (Throwable t) {
1245             log("ManagerServlet.resources[" + type + "]", t);
1246             writer.println(sm.getString("managerServlet.exception",
1247                                         t.toString()));
1248         }
1249 
1250     }
1251 
1252 
1253     /**
1254      * Render a list of security role names (and corresponding descriptions)
1255      * from the <code>org.apache.catalina.UserDatabase</code> resource that is
1256      * connected to the <code>users</code> resource reference.  Typically, this
1257      * will be the global user database, but can be adjusted if you have
1258      * different user databases for different virtual hosts.
1259      *
1260      * @param writer Writer to render to
1261      */
1262     protected void roles(PrintWriter writer) {
1263 
1264         if (debug >= 1) {
1265             log("roles:  List security roles from user database");
1266         }
1267 
1268         // Look up the UserDatabase instance we should use
1269         UserDatabase database = null;
1270         try {
1271             InitialContext ic = new InitialContext();
1272             database = (UserDatabase) ic.lookup("java:comp/env/users");
1273         } catch (NamingException e) {
1274             writer.println(sm.getString("managerServlet.userDatabaseError"));
1275             log("java:comp/env/users", e);
1276             return;
1277         }
1278         if (database == null) {
1279             writer.println(sm.getString("managerServlet.userDatabaseMissing"));
1280             return;
1281         }
1282 
1283         // Enumerate the available roles
1284         writer.println(sm.getString("managerServlet.rolesList"));
1285         Iterator roles = database.getRoles();
1286         if (roles != null) {
1287             while (roles.hasNext()) {
1288                 Role role = (Role) roles.next();
1289                 writer.print(role.getRolename());
1290                 writer.print(':');
1291                 if (role.getDescription() != null) {
1292                     writer.print(role.getDescription());
1293                 }
1294                 writer.println();
1295             }
1296         }
1297 
1298 
1299     }
1300 
1301 
1302     /**
1303      * Writes System OS and JVM properties.
1304      * @param writer Writer to render to
1305      */
1306     protected void serverinfo(PrintWriter writer) {
1307         if (debug >= 1)
1308             log("serverinfo");
1309         try {
1310             StringBuffer props = new StringBuffer();
1311             props.append("OK - Server info");
1312             props.append("\nTomcat Version: ");
1313             props.append(ServerInfo.getServerInfo());
1314             props.append("\nOS Name: ");
1315             props.append(System.getProperty("os.name"));
1316             props.append("\nOS Version: ");
1317             props.append(System.getProperty("os.version"));
1318             props.append("\nOS Architecture: ");
1319             props.append(System.getProperty("os.arch"));
1320             props.append("\nJVM Version: ");
1321             props.append(System.getProperty("java.runtime.version"));
1322             props.append("\nJVM Vendor: ");
1323             props.append(System.getProperty("java.vm.vendor"));
1324             writer.println(props.toString());
1325         } catch (Throwable t) {
1326             getServletContext().log("ManagerServlet.serverinfo",t);
1327             writer.println(sm.getString("managerServlet.exception",
1328                                         t.toString()));
1329         }
1330     }
1331 
1332     /**
1333      * Session information for the web application at the specified context path.
1334      * Displays a profile of session MaxInactiveInterval timeouts listing number
1335      * of sessions for each 10 minute timeout interval up to 10 hours.
1336      *
1337      * @param writer Writer to render to
1338      * @param vhost Virtual host to be listed
1339      * @param path Context path of the application to list session information for
1340      */
1341     protected void sessions(PrintWriter writer, String vhost, String path) {
1342 
1343         if (debug >= 1)
1344             log("sessions: Session information for web application at '" + vhost + ":" + path + "'");
1345 
1346         if(vhost == null || vhost.length() == 0) {
1347             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1348             return;
1349         }        
1350 
1351         Deployer deployer = (Deployer) engine.findChild(vhost);
1352         if (deployer == null) {
1353             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1354             return;                
1355         }
1356 
1357         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1358             writer.println(sm.getString("managerServlet.invalidPath", path));
1359             return;
1360         }
1361         String displayPath = path;
1362         if( path.equals("/") )
1363             path = "";
1364         try {
1365             Context context = deployer.findDeployedApp(path);
1366             if (context == null) {
1367                 writer.println(sm.getString("managerServlet.noContext", displayPath));
1368                 return;
1369             }
1370             writer.println(sm.getString("managerServlet.sessions", displayPath));
1371             writer.println(sm.getString("managerServlet.sessiondefaultmax",
1372                                 "" + context.getManager().getMaxInactiveInterval()/60));
1373             Session [] sessions = context.getManager().findSessions();
1374             int [] timeout = new int[60];
1375             int notimeout = 0;
1376             for (int i = 0; i < sessions.length; i++) {
1377                 int time = sessions[i].getMaxInactiveInterval()/(10*60);
1378                 if (time < 0)
1379                     notimeout++;
1380                 else if (time >= timeout.length)
1381                     timeout[timeout.length-1]++;
1382                 else
1383                     timeout[time]++;
1384             }
1385             if (timeout[0] > 0)
1386                 writer.println(sm.getString("managerServlet.sessiontimeout",
1387                                             "<10", "" + timeout[0]));
1388             for (int i = 1; i < timeout.length-1; i++) {
1389                 if (timeout[i] > 0)
1390                     writer.println(sm.getString("managerServlet.sessiontimeout",
1391                                      "" + (i)*10 + " - <" + (i+1)*10,
1392                                                 "" + timeout[i]));
1393             }
1394             if (timeout[timeout.length-1] > 0)
1395                 writer.println(sm.getString("managerServlet.sessiontimeout",
1396                                             ">=" + timeout.length*10,
1397                                             "" + timeout[timeout.length-1]));
1398             if (notimeout > 0)
1399                 writer.println(sm.getString("managerServlet.sessiontimeout",
1400                                             "unlimited","" + notimeout));
1401         } catch (Throwable t) {
1402             log("ManagerServlet.sessions[" + displayPath + "]", t);
1403             writer.println(sm.getString("managerServlet.exception",
1404                                         t.toString()));
1405         }
1406 
1407     }
1408 
1409 
1410     /**
1411      * Start the web application at the specified context path.
1412      *
1413      * @param writer Writer to render to
1414      * @param vhost Virtual host to be listed
1415      * @param path Context path of the application to be started
1416      */
1417     protected void start(PrintWriter writer, String vhost, String path) {
1418 
1419         if (debug >= 1)
1420             log("start: Starting web application at '" + vhost + ":" + path + "'");
1421 
1422         if(vhost == null || vhost.length() == 0) {
1423             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1424             return;
1425         }        
1426 
1427         Deployer deployer = (Deployer) engine.findChild(vhost);
1428         if (deployer == null) {
1429             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1430             return;                
1431         }
1432         
1433         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1434             writer.println(sm.getString("managerServlet.invalidPath", path));
1435             return;
1436         }
1437         String displayPath = path;
1438         if( path.equals("/") )
1439             path = "";
1440 
1441         try {
1442             Context context = deployer.findDeployedApp(path);
1443             if (context == null) {
1444                 writer.println(sm.getString("managerServlet.noContext", 
1445                                             displayPath));
1446                 return;
1447             }
1448             deployer.start(path);
1449             if (context.getAvailable())
1450                 writer.println
1451                     (sm.getString("managerServlet.started", displayPath));
1452             else
1453                 writer.println
1454                     (sm.getString("managerServlet.startFailed", displayPath));
1455         } catch (Throwable t) {
1456             getServletContext().log
1457                 (sm.getString("managerServlet.startFailed", displayPath), t);
1458             writer.println
1459                 (sm.getString("managerServlet.startFailed", displayPath));
1460             writer.println(sm.getString("managerServlet.exception",
1461                                         t.toString()));
1462         }
1463 
1464     }
1465 
1466 
1467     /**
1468      * Stop the web application at the specified context path.
1469      *
1470      * @param writer Writer to render to
1471      * @param vhost Virtual host to be listed
1472      * @param path Context path of the application to be stopped
1473      */
1474     protected void stop(PrintWriter writer, String vhost, String path) {
1475 
1476         if (debug >= 1)
1477             log("stop: Stopping web application at '" + vhost + ":" + path + "'");
1478 
1479         if(vhost == null || vhost.length() == 0) {
1480             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1481             return;
1482         }        
1483 
1484         Deployer deployer = (Deployer) engine.findChild(vhost);
1485         if (deployer == null) {
1486             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1487             return;                
1488         }
1489 
1490         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1491             writer.println(sm.getString("managerServlet.invalidPath", path));
1492             return;
1493         }
1494         String displayPath = path;
1495         if( path.equals("/") )
1496             path = "";
1497 
1498         try {
1499             Context context = deployer.findDeployedApp(path);
1500             if (context == null) {
1501                 writer.println(sm.getString("managerServlet.noContext", 
1502                                             displayPath));
1503                 return;
1504             }
1505             // It isn't possible for the manager to stop itself
1506             if (context.getPath().equals(this.context.getPath())) {
1507                 writer.println(sm.getString("managerServlet.noSelf"));
1508                 return;
1509             }
1510             deployer.stop(path);
1511             writer.println(sm.getString("managerServlet.stopped", displayPath));
1512         } catch (Throwable t) {
1513             log("ManagerServlet.stop[" + displayPath + "]", t);
1514             writer.println(sm.getString("managerServlet.exception",
1515                                         t.toString()));
1516         }
1517 
1518     }
1519 
1520 
1521     /**
1522      * Undeploy the web application at the specified context path.
1523      *
1524      * @param writer Writer to render to
1525      * @param vhost Virtual host to be listed
1526      * @param path Context path of the application to be removed
1527      */
1528     protected void undeploy(PrintWriter writer, String vhost, String path) {
1529 
1530         if (debug >= 1)
1531             log("undeploy: Undeploying web application at '" + vhost + ":" + path + "'");
1532 
1533         if(vhost == null || vhost.length() == 0) {
1534             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1535             return;
1536         }        
1537 
1538         Deployer deployer = (Deployer) engine.findChild(vhost);
1539         if (deployer == null) {
1540             writer.println(sm.getString("managerServlet.invalidVirtualHost", vhost));
1541             return;                
1542         }
1543 
1544         if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
1545             writer.println(sm.getString("managerServlet.invalidPath", path));
1546             return;
1547         }
1548         String displayPath = path;
1549         if( path.equals("/") )
1550             path = "";
1551 
1552         try {
1553 
1554             // Validate the Context of the specified application
1555             Context context = deployer.findDeployedApp(path);
1556             if (context == null) {
1557                 writer.println(sm.getString("managerServlet.noContext",
1558                                             displayPath));
1559                 return;
1560             }
1561 
1562             // Identify the appBase of the owning Host of this Context (if any)
1563             String appBase = null;
1564             File appBaseDir = null;
1565             if (context.getParent() instanceof Host) {
1566                 appBase = ((Host) context.getParent()).getAppBase();
1567                 appBaseDir = new File(appBase);
1568                 if (!appBaseDir.isAbsolute()) {
1569                     appBaseDir = new File(System.getProperty("catalina.base"),
1570                                           appBase);
1571                 }
1572             }
1573 
1574             // Validate the docBase path of this application
1575             File deployed = getDeployed(context);
1576             String deployedPath = deployed.getCanonicalPath();
1577             String docBase = context.getDocBase();
1578             File docBaseDir = new File(docBase);
1579             if (!docBaseDir.isAbsolute()) {
1580                 docBaseDir = new File(appBaseDir, docBase);
1581             }
1582             String docBasePath = docBaseDir.getCanonicalPath();
1583             boolean deleteDir = true;
1584             if (!docBasePath.startsWith(deployedPath)) {
1585                 deleteDir = false;
1586             }
1587 
1588             // Remove this web application and its associated docBase
1589             if (debug >= 2) {
1590                 log("Undeploying document base " + docBasePath);
1591             }
1592             // It isn't possible for the manager to undeploy itself
1593             if (context.getPath().equals(this.context.getPath())) {
1594                 writer.println(sm.getString("managerServlet.noSelf"));
1595                 return;
1596             }
1597             boolean dir = docBaseDir.isDirectory();
1598             deployer.remove(path, true);
1599             if (deleteDir) {
1600                 if (dir) {
1601                     undeployDir(docBaseDir);
1602                     // Delete the WAR file
1603                     File docBaseWar = new File(docBasePath + ".war");
1604                     docBaseWar.delete();
1605                 } else {
1606                     // Delete the WAR file
1607                     docBaseDir.delete();
1608                 }
1609             }
1610             File docBaseXml = new File(context.getConfigFile());
1611             docBaseXml.delete();
1612             writer.println(sm.getString("managerServlet.undeployed",
1613                                         displayPath));
1614 
1615         } catch (Throwable t) {
1616             log("ManagerServlet.undeploy[" + displayPath + "]", t);
1617             writer.println(sm.getString("managerServlet.exception",
1618                                         t.toString()));
1619         }
1620 
1621     }
1622 
1623 
1624     // -------------------------------------------------------- Support Methods
1625 
1626 
1627     /**
1628      * Given a context path, get the config file name.
1629      */
1630     protected String getConfigFile(String path) {
1631         String basename = null;
1632         if (path.equals("")) {
1633             basename = "ROOT";
1634         } else {
1635             basename = path.substring(1).replace('/', '#');
1636         }
1637         return (basename);
1638     }
1639 
1640 
1641     /**
1642      * Extract the context configuration file from the specified WAR,
1643      * if it is present.  If it is not present, ensure that the corresponding
1644      * file does not exist.
1645      *
1646      * @param war File object representing the WAR
1647      * @param xml File object representing where to store the extracted
1648      *  context configuration file (if it exists)
1649      *
1650      * @exception IOException if an i/o error occurs
1651      */
1652     protected void extractXml(File war, File xml) throws IOException {
1653 
1654         xml.delete();
1655         JarFile jar = null;
1656         JarEntry entry = null;
1657         InputStream istream = null;
1658         BufferedOutputStream ostream = null;
1659         try {
1660             jar = new JarFile(war);
1661             entry = jar.getJarEntry("META-INF/context.xml");
1662             if (entry == null) {
1663                 return;
1664             }
1665             istream = jar.getInputStream(entry);
1666             ostream =
1667                 new BufferedOutputStream(new FileOutputStream(xml), 1024);
1668             byte buffer[] = new byte[1024];
1669             while (true) {
1670                 int n = istream.read(buffer);
1671                 if (n < 0) {
1672                     break;
1673                 }
1674                 ostream.write(buffer, 0, n);
1675             }
1676             ostream.flush();
1677             ostream.close();
1678             ostream = null;
1679             istream.close();
1680             istream = null;
1681             entry = null;
1682             jar.close();
1683             jar = null;
1684         } catch (IOException e) {
1685             xml.delete();
1686             throw e;
1687         } finally {
1688             if (ostream != null) {
1689                 try {
1690                     ostream.close();
1691                 } catch (Throwable t) {
1692                     ;
1693                 }
1694                 ostream = null;
1695             }
1696             if (istream != null) {
1697                 try {
1698                     istream.close();
1699                 } catch (Throwable t) {
1700                     ;
1701                 }
1702                 istream = null;
1703             }
1704             entry = null;
1705             if (jar != null) {
1706                 try {
1707                     jar.close();
1708                 } catch (Throwable t) {
1709                     ;
1710                 }
1711                 jar = null;
1712             }
1713         }
1714 
1715     }
1716 
1717 
1718     /**
1719      * Delete the specified directory, including all of its contents and
1720      * subdirectories recursively.
1721      *
1722      * @param dir File object representing the directory to be deleted
1723      */
1724     protected void undeployDir(File dir) {
1725 
1726         String files[] = dir.list();
1727         if (files == null) {
1728             files = new String[0];
1729         }
1730         for (int i = 0; i < files.length; i++) {
1731             File file = new File(dir, files[i]);
1732             if (file.isDirectory()) {
1733                 undeployDir(file);
1734             } else {
1735                 file.delete();
1736             }
1737         }
1738         dir.delete();
1739 
1740     }
1741 
1742 
1743     /**
1744      * Upload the WAR file included in this request, and store it at the
1745      * specified file location.
1746      *
1747      * @param request The servlet request we are processing
1748      * @param war The file into which we should store the uploaded WAR
1749      *
1750      * @exception IOException if an I/O error occurs during processing
1751      */
1752     protected void uploadWar(HttpServletRequest request, File war)
1753         throws IOException {
1754 
1755         war.delete();
1756         ServletInputStream istream = null;
1757         BufferedOutputStream ostream = null;
1758         try {
1759             istream = request.getInputStream();
1760             ostream =
1761                 new BufferedOutputStream(new FileOutputStream(war), 1024);
1762             byte buffer[] = new byte[1024];
1763             while (true) {
1764                 int n = istream.read(buffer);
1765                 if (n < 0) {
1766                     break;
1767                 }
1768                 ostream.write(buffer, 0, n);
1769             }
1770             ostream.flush();
1771             ostream.close();
1772             ostream = null;
1773             istream.close();
1774             istream = null;
1775         } catch (IOException e) {
1776             war.delete();
1777             throw e;
1778         } finally {
1779             if (ostream != null) {
1780                 try {
1781                     ostream.close();
1782                 } catch (Throwable t) {
1783                     ;
1784                 }
1785                 ostream = null;
1786             }
1787             if (istream != null) {
1788                 try {
1789                     istream.close();
1790                 } catch (Throwable t) {
1791                     ;
1792                 }
1793                 istream = null;
1794             }
1795         }
1796 
1797     }
1798 
1799 
1800     /**
1801      * Copy a file.
1802      */
1803     private boolean copy(File src, File dest) {
1804         FileInputStream is = null;
1805         FileOutputStream os = null;
1806         try {
1807             is = new FileInputStream(src);
1808             os = new FileOutputStream(dest);
1809             byte[] buf = new byte[4096];
1810             while (true) {
1811                 int len = is.read(buf);
1812                 if (len < 0)
1813                     break;
1814                 os.write(buf, 0, len);
1815             }
1816             is.close();
1817             os.close();
1818         } catch (IOException e) {
1819             return false;
1820         } finally {
1821             try {
1822                 if (is != null) {
1823                     is.close();
1824                 }
1825             } catch (Exception e) {
1826                 // Ignore
1827             }
1828             try {
1829                 if (os != null) {
1830                     os.close();
1831                 }
1832             } catch (Exception e) {
1833                 // Ignore
1834             }
1835         }
1836         return true;
1837     }
1838 
1839     /**
1840      * Identify the appBase of the Host of Context
1841      * (if any)
1842      * 
1843      * @param context context to obtain appBase
1844      * 
1845      * @return File representing appBase directory 
1846      */
1847     private File getDeployed(Context context) {
1848         
1849         File deployed = null;
1850         
1851         String appBase = ((Host) context.getParent()).getAppBase();
1852         deployed = new File(appBase);
1853         if (!deployed.isAbsolute()) {
1854             deployed = new File(System.getProperty("catalina.base"),
1855                                 appBase);
1856         }
1857         
1858         return deployed;
1859         
1860     }
1861     
1862 }