/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.manager; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.naming.Binding; import javax.naming.InitialContext; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.catalina.Container; import org.apache.catalina.ContainerServlet; import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.Globals; import org.apache.catalina.Host; import org.apache.catalina.Lifecycle; import org.apache.catalina.Manager; import org.apache.catalina.Role; import org.apache.catalina.Server; import org.apache.catalina.ServerFactory; import org.apache.catalina.Session; import org.apache.catalina.UserDatabase; import org.apache.catalina.Wrapper; import org.apache.catalina.core.StandardHost; import org.apache.catalina.core.StandardServer; import org.apache.catalina.util.RequestUtil; import org.apache.catalina.util.ServerInfo; import org.apache.catalina.util.StringManager; import org.apache.tomcat.util.modeler.Registry; /** * Servlet that enables remote management of the web applications installed * within the same virtual host as this web application is. Normally, this * functionality will be protected by a security constraint in the web * application deployment descriptor. However, this requirement can be * relaxed during testing. * <p> * This servlet examines the value returned by <code>getPathInfo()</code> * and related query parameters to determine what action is being requested. * The following actions and parameters (starting after the servlet path) * are supported: * <ul> * <li><b>/deploy?config={config-url}</b> - Install and start a new * web application, based on the contents of the context configuration * file found at the specified URL. The <code>docBase</code> attribute * of the context configuration file is used to locate the actual * WAR or directory containing the application.</li> * <li><b>/deploy?config={config-url}&war={war-url}/</b> - Install and start * a new web application, based on the contents of the context * configuration file found at <code>{config-url}</code>, overriding the * <code>docBase</code> attribute with the contents of the web * application archive found at <code>{war-url}</code>.</li> * <li><b>/deploy?path=/xxx&war={war-url}</b> - Install and start a new * web application attached to context path <code>/xxx</code>, based * on the contents of the web application archive found at the * specified URL.</li> * <li><b>/list</b> - List the context paths of all currently installed web * applications for this virtual host. Each context will be listed with * the following format <code>path:status:sessions</code>. * Where path is the context path. Status is either running or stopped. * Sessions is the number of active Sessions.</li> * <li><b>/reload?path=/xxx</b> - Reload the Java classes and resources for * the application at the specified path.</li> * <li><b>/resources?type=xxxx</b> - Enumerate the available global JNDI * resources, optionally limited to those of the specified type * (fully qualified Java class name), if available.</li> * <li><b>/roles</b> - Enumerate the available security role names and * descriptions from the user database connected to the <code>users</code> * resource reference. * <li><b>/serverinfo</b> - Display system OS and JVM properties. * <li><b>/sessions</b> - Deprecated. Use expire. * <li><b>/expire?path=/xxx</b> - List session idle timeinformation about the * web application attached to context path <code>/xxx</code> for this * virtual host.</li> * <li><b>/expire?path=/xxx&idle=mm</b> - Expire sessions * for the context path <code>/xxx</code> which were idle for at * least mm minutes.</li> * <li><b>/start?path=/xxx</b> - Start the web application attached to * context path <code>/xxx</code> for this virtual host.</li> * <li><b>/stop?path=/xxx</b> - Stop the web application attached to * context path <code>/xxx</code> for this virtual host.</li> * <li><b>/undeploy?path=/xxx</b> - Shutdown and remove the web application * attached to context path <code>/xxx</code> for this virtual host, * and remove the underlying WAR file or document base directory. * (<em>NOTE</em> - This is only allowed if the WAR file or document * base is stored in the <code>appBase</code> directory of this host, * typically as a result of being placed there via the <code>/deploy</code> * command.</li> * </ul> * <p>Use <code>path=/</code> for the ROOT context.</p> * <p>The syntax of the URL for a web application archive must conform to one * of the following patterns to be successfully deployed:</p> * <ul> * <li><b>file:/absolute/path/to/a/directory</b> - You can specify the absolute * path of a directory that contains the unpacked version of a web * application. This directory will be attached to the context path you * specify without any changes.</li> * </ul> * <p> * <b>NOTE</b> - Attempting to reload or remove the application containing * this servlet itself will not succeed. Therefore, this servlet should * generally be deployed as a separate web application within the virtual host * to be managed. * <p> * <b>NOTE</b> - For security reasons, this application will not operate * when accessed via the invoker servlet. You must explicitly map this servlet * with a servlet mapping, and you will always want to protect it with * appropriate security constraints as well. * <p> * The following servlet initialization parameters are recognized: * <ul> * <li><b>debug</b> - The debugging detail level that controls the amount * of information that is logged by this servlet. Default is zero. * </ul> * * @author Craig R. McClanahan * @author Remy Maucherat * */ public class ManagerServlet extends HttpServlet implements ContainerServlet { // ----------------------------------------------------- Instance Variables /** * Path where context descriptors should be deployed. */ protected File configBase = null; /** * The Context container associated with our web application. */ protected Context context = null; /** * The debugging detail level for this servlet. */ protected int debug = 1; /** * File object representing the directory into which the deploy() command * will store the WAR and context configuration files that have been * uploaded. */ protected File deployed = null; /** * Path used to store revisions of webapps. */ protected File versioned = null; /** * Path used to store context descriptors. */ protected File contextDescriptors = null; /** * The associated host. */ protected Host host = null; /** * The host appBase. */ protected File appBase = null; /** * MBean server. */ protected MBeanServer mBeanServer = null; /** * The associated deployer ObjectName. */ protected ObjectName oname = null; /** * The global JNDI <code>NamingContext</code> for this server, * if available. */ protected javax.naming.Context global = null; /** * The string manager for this package. */ protected static StringManager sm = StringManager.getManager(Constants.Package); /** * The Wrapper container associated with this servlet. */ protected Wrapper wrapper = null; // ----------------------------------------------- ContainerServlet Methods /** * Return the Wrapper with which we are associated. */ public Wrapper getWrapper() { return (this.wrapper); } /** * Set the Wrapper with which we are associated. * * @param wrapper The new wrapper */ public void setWrapper(Wrapper wrapper) { this.wrapper = wrapper; if (wrapper == null) { context = null; host = null; oname = null; } else { context = (Context) wrapper.getParent(); host = (Host) context.getParent(); Engine engine = (Engine) host.getParent(); try { oname = new ObjectName(engine.getName() + ":type=Deployer,host=" + host.getName()); } catch (Exception e) { // ? } } // Retrieve the MBean server mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); } // --------------------------------------------------------- Public Methods /** * Finalize this servlet. */ public void destroy() { ; // No actions necessary } /** * Process a GET request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Verify that we were not accessed using the invoker servlet if (request.getAttribute(Globals.INVOKED_ATTR) != null) throw new UnavailableException (sm.getString("managerServlet.cannotInvoke")); // Identify the request parameters that we need String command = request.getPathInfo(); if (command == null) command = request.getServletPath(); String config = request.getParameter("config"); String path = request.getParameter("path"); String type = request.getParameter("type"); String war = request.getParameter("war"); String tag = request.getParameter("tag"); boolean update = false; if ((request.getParameter("update") != null) && (request.getParameter("update").equals("true"))) { update = true; } // Prepare our output writer to generate the response message response.setContentType("text/plain; charset=" + Constants.CHARSET); PrintWriter writer = response.getWriter(); // Process the requested command (note - "/deploy" is not listed here) if (command == null) { writer.println(sm.getString("managerServlet.noCommand")); } else if (command.equals("/deploy")) { if (war != null || config != null) { deploy(writer, config, path, war, update); } else { deploy(writer, path, tag); } } else if (command.equals("/install")) { // Deprecated deploy(writer, config, path, war, false); } else if (command.equals("/list")) { list(writer); } else if (command.equals("/reload")) { reload(writer, path); } else if (command.equals("/remove")) { // Deprecated undeploy(writer, path); } else if (command.equals("/resources")) { resources(writer, type); } else if (command.equals("/roles")) { roles(writer); } else if (command.equals("/save")) { save(writer, path); } else if (command.equals("/serverinfo")) { serverinfo(writer); } else if (command.equals("/sessions")) { expireSessions(writer, path, request); } else if (command.equals("/expire")) { expireSessions(writer, path, request); } else if (command.equals("/start")) { start(writer, path); } else if (command.equals("/stop")) { stop(writer, path); } else if (command.equals("/undeploy")) { undeploy(writer, path); } else if (command.equals("/findleaks")) { findleaks(writer); } else { writer.println(sm.getString("managerServlet.unknownCommand", command)); } // Finish up the response writer.flush(); writer.close(); } /** * Process a PUT request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs */ public void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Verify that we were not accessed using the invoker servlet if (request.getAttribute(Globals.INVOKED_ATTR) != null) throw new UnavailableException (sm.getString("managerServlet.cannotInvoke")); // Identify the request parameters that we need String command = request.getPathInfo(); if (command == null) command = request.getServletPath(); String path = request.getParameter("path"); String tag = request.getParameter("tag"); boolean update = false; if ((request.getParameter("update") != null) && (request.getParameter("update").equals("true"))) { update = true; } // Prepare our output writer to generate the response message response.setContentType("text/plain;charset="+Constants.CHARSET); PrintWriter writer = response.getWriter(); // Process the requested command if (command == null) { writer.println(sm.getString("managerServlet.noCommand")); } else if (command.equals("/deploy")) { deploy(writer, path, tag, update, request); } else { writer.println(sm.getString("managerServlet.unknownCommand", command)); } // Finish up the response writer.flush(); writer.close(); } /** * Initialize this servlet. */ public void init() throws ServletException { // Ensure that our ContainerServlet properties have been set if ((wrapper == null) || (context == null)) throw new UnavailableException (sm.getString("managerServlet.noWrapper")); // Verify that we were not accessed using the invoker servlet String servletName = getServletConfig().getServletName(); if (servletName == null) servletName = ""; if (servletName.startsWith("org.apache.catalina.INVOKER.")) throw new UnavailableException (sm.getString("managerServlet.cannotInvoke")); // Set our properties from the initialization parameters String value = null; try { value = getServletConfig().getInitParameter("debug"); debug = Integer.parseInt(value); } catch (Throwable t) { ; } // Acquire global JNDI resources if available Server server = ServerFactory.getServer(); if ((server != null) && (server instanceof StandardServer)) { global = ((StandardServer) server).getGlobalNamingContext(); } // Calculate the directory into which we will be deploying applications versioned = (File) getServletContext().getAttribute ("javax.servlet.context.tempdir"); // Identify the appBase of the owning Host of this Context // (if any) String appBase = ((Host) context.getParent()).getAppBase(); deployed = new File(appBase); if (!deployed.isAbsolute()) { deployed = new File(System.getProperty("catalina.base"), appBase); } configBase = new File(System.getProperty("catalina.base"), "conf"); Container container = context; Container host = null; Container engine = null; while (container != null) { if (container instanceof Host) host = container; if (container instanceof Engine) engine = container; container = container.getParent(); } if (engine != null) { configBase = new File(configBase, engine.getName()); } if (host != null) { configBase = new File(configBase, host.getName()); } // Note: The directory must exist for this to work. // Log debugging messages as necessary if (debug >= 1) { log("init: Associated with Deployer '" + oname + "'"); if (global != null) { log("init: Global resources are available"); } } } // -------------------------------------------------------- Private Methods /** * Find potential memory leaks caused by web application reload. */ protected void findleaks(PrintWriter writer) { if (!(host instanceof StandardHost)) { writer.println(sm.getString("managerServlet.findleaksFail")); return; } String[] results = ((StandardHost) host).findReloadedContextMemoryLeaks(); for (String result : results) { if ("".equals(result)) { result = "/"; } writer.println(result); } } /** * Store server configuration. * * @param path Optional context path to save */ protected synchronized void save(PrintWriter writer, String path) { Server server = ServerFactory.getServer(); if (!(server instanceof StandardServer)) { writer.println(sm.getString("managerServlet.saveFail", server)); return; } if ((path == null) || path.length() == 0 || !path.startsWith("/")) { try { ((StandardServer) server).storeConfig(); writer.println(sm.getString("managerServlet.saved")); } catch (Exception e) { log("managerServlet.storeConfig", e); writer.println(sm.getString("managerServlet.exception", e.toString())); return; } } else { String contextPath = path; if (path.equals("/")) { contextPath = ""; } Context context = (Context) host.findChild(contextPath); if (context == null) { writer.println(sm.getString("managerServlet.noContext", path)); return; } try { ((StandardServer) server).storeContext(context); writer.println(sm.getString("managerServlet.savedContext", path)); } catch (Exception e) { log("managerServlet.save[" + path + "]", e); writer.println(sm.getString("managerServlet.exception", e.toString())); return; } } } /** * Deploy a web application archive (included in the current request) * at the specified context path. * * @param writer Writer to render results to * @param path Context path of the application to be installed * @param tag Tag to be associated with the webapp * @param request Servlet request we are processing */ protected synchronized void deploy (PrintWriter writer, String path, String tag, boolean update, HttpServletRequest request) { if (debug >= 1) { log("deploy: Deploying web application at '" + path + "'"); } // Validate the requested context path if ((path == null) || path.length() == 0 || !path.startsWith("/")) { writer.println(sm.getString("managerServlet.invalidPath", path)); return; } String displayPath = path; if( path.equals("/") ) path = ""; String basename = getDocBase(path); // Check if app already exists, or undeploy it if updating Context context = (Context) host.findChild(path); if (update) { if (context != null) { undeploy(writer, displayPath); } context = (Context) host.findChild(path); } if (context != null) { writer.println (sm.getString("managerServlet.alreadyContext", displayPath)); return; } // Calculate the base path File deployedPath = deployed; if (tag != null) { deployedPath = new File(versioned, tag); deployedPath.mkdirs(); } // Upload the web application archive to a local WAR file File localWar = new File(deployedPath, basename + ".war"); if (debug >= 2) { log("Uploading WAR file to " + localWar); } // Copy WAR to appBase try { if (!isServiced(path)) { addServiced(path); try { // Upload WAR uploadWar(request, localWar); // Copy WAR and XML to the host app base if needed if (tag != null) { deployedPath = deployed; File localWarCopy = new File(deployedPath, basename + ".war"); copy(localWar, localWarCopy); localWar = localWarCopy; copy(localWar, new File(getAppBase(), basename + ".war")); } // Perform new deployment check(path); } finally { removeServiced(path); } } } catch (Exception e) { log("managerServlet.check[" + displayPath + "]", e); writer.println(sm.getString("managerServlet.exception", e.toString())); return; } context = (Context) host.findChild(path); if (context != null && context.getConfigured()) { writer.println(sm.getString("managerServlet.deployed", displayPath)); } else { // Something failed writer.println(sm.getString("managerServlet.deployFailed", displayPath)); } } /** * Install an application for the specified path from the specified * web application archive. * * @param writer Writer to render results to * @param tag Revision tag to deploy from * @param path Context path of the application to be installed */ protected void deploy(PrintWriter writer, String path, String tag) { // Validate the requested context path if ((path == null) || path.length() == 0 || !path.startsWith("/")) { writer.println(sm.getString("managerServlet.invalidPath", path)); return; } String displayPath = path; if( path.equals("/") ) path = ""; // Calculate the base path File deployedPath = versioned; if (tag != null) { deployedPath = new File(deployedPath, tag); } // Find the local WAR file File localWar = new File(deployedPath, getDocBase(path) + ".war"); // Find the local context deployment file (if any) File localXml = new File(configBase, getConfigFile(path) + ".xml"); // Check if app already exists, or undeploy it if updating Context context = (Context) host.findChild(path); if (context != null) { undeploy(writer, displayPath); } // Copy WAR to appBase try { if (!isServiced(path)) { addServiced(path); try { copy(localWar, new File(getAppBase(), getDocBase(path) + ".war")); // Perform new deployment check(path); } finally { removeServiced(path); } } } catch (Exception e) { log("managerServlet.check[" + displayPath + "]", e); writer.println(sm.getString("managerServlet.exception", e.toString())); return; } context = (Context) host.findChild(path); if (context != null && context.getConfigured()) { writer.println(sm.getString("managerServlet.deployed", displayPath)); } else { // Something failed writer.println(sm.getString("managerServlet.deployFailed", displayPath)); } } /** * Install an application for the specified path from the specified * web application archive. * * @param writer Writer to render results to * @param config URL of the context configuration file to be installed * @param path Context path of the application to be installed * @param war URL of the web application archive to be installed * @param update true to override any existing webapp on the path */ protected void deploy(PrintWriter writer, String config, String path, String war, boolean update) { if (config != null && config.length() == 0) { config = null; } if (war != null && war.length() == 0) { war = null; } if (debug >= 1) { if (config != null && config.length() > 0) { if (war != null) { log("install: Installing context configuration at '" + config + "' from '" + war + "'"); } else { log("install: Installing context configuration at '" + config + "'"); } } else { if (path != null && path.length() > 0) { log("install: Installing web application at '" + path + "' from '" + war + "'"); } else { log("install: Installing web application from '" + war + "'"); } } } if (path == null || path.length() == 0 || !path.startsWith("/")) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if("/".equals(path)) { path = ""; } // Check if app already exists, or undeploy it if updating Context context = (Context) host.findChild(path); if (update) { if (context != null) { undeploy(writer, displayPath); } context = (Context) host.findChild(path); } if (context != null) { writer.println (sm.getString("managerServlet.alreadyContext", displayPath)); return; } if (config != null && (config.startsWith("file:"))) { config = config.substring("file:".length()); } if (war != null && (war.startsWith("file:"))) { war = war.substring("file:".length()); } try { if (!isServiced(path)) { addServiced(path); try { if (config != null) { configBase.mkdirs(); copy(new File(config), new File(configBase, getConfigFile(path) + ".xml")); } if (war != null) { if (war.endsWith(".war")) { copy(new File(war), new File(getAppBase(), getDocBase(path) + ".war")); } else { copy(new File(war), new File(getAppBase(), getDocBase(path))); } } // Perform new deployment check(path); } finally { removeServiced(path); } } context = (Context) host.findChild(path); if (context != null && context.getConfigured() && context.getAvailable()) { writer.println(sm.getString("managerServlet.deployed", displayPath)); } else if (context!=null && !context.getAvailable()) { writer.println(sm.getString("managerServlet.deployedButNotStarted", displayPath)); } else { // Something failed writer.println(sm.getString("managerServlet.deployFailed", displayPath)); } } catch (Throwable t) { log("ManagerServlet.install[" + displayPath + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Render a list of the currently active Contexts in our virtual host. * * @param writer Writer to render to */ protected void list(PrintWriter writer) { if (debug >= 1) log("list: Listing contexts for virtual host '" + host.getName() + "'"); writer.println(sm.getString("managerServlet.listed", host.getName())); Container[] contexts = host.findChildren(); for (int i = 0; i < contexts.length; i++) { Context context = (Context) contexts[i]; String displayPath = context.getPath(); if( displayPath.equals("") ) displayPath = "/"; if (context != null ) { if (context.getAvailable()) { writer.println(sm.getString("managerServlet.listitem", displayPath, "running", "" + context.getManager().findSessions().length, context.getDocBase())); } else { writer.println(sm.getString("managerServlet.listitem", displayPath, "stopped", "0", context.getDocBase())); } } } } /** * Reload the web application at the specified context path. * * @param writer Writer to render to * @param path Context path of the application to be restarted */ protected void reload(PrintWriter writer, String path) { if (debug >= 1) log("restart: Reloading web application at '" + path + "'"); if ((path == null) || (!path.startsWith("/") && path.equals(""))) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if( path.equals("/") ) path = ""; try { Context context = (Context) host.findChild(path); if (context == null) { writer.println(sm.getString ("managerServlet.noContext", RequestUtil.filter(displayPath))); return; } // It isn't possible for the manager to reload itself if (context.getPath().equals(this.context.getPath())) { writer.println(sm.getString("managerServlet.noSelf")); return; } context.reload(); writer.println (sm.getString("managerServlet.reloaded", displayPath)); } catch (Throwable t) { log("ManagerServlet.reload[" + displayPath + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Render a list of available global JNDI resources. * * @param type Fully qualified class name of the resource type of interest, * or <code>null</code> to list resources of all types */ protected void resources(PrintWriter writer, String type) { if (debug >= 1) { if (type != null) { log("resources: Listing resources of type " + type); } else { log("resources: Listing resources of all types"); } } // Is the global JNDI resources context available? if (global == null) { writer.println(sm.getString("managerServlet.noGlobal")); return; } // Enumerate the global JNDI resources of the requested type if (type != null) { writer.println(sm.getString("managerServlet.resourcesType", type)); } else { writer.println(sm.getString("managerServlet.resourcesAll")); } Class clazz = null; try { if (type != null) { clazz = Class.forName(type); } } catch (Throwable t) { log("ManagerServlet.resources[" + type + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); return; } printResources(writer, "", global, type, clazz); } /** * List the resources of the given context. */ protected void printResources(PrintWriter writer, String prefix, javax.naming.Context namingContext, String type, Class clazz) { try { NamingEnumeration items = namingContext.listBindings(""); while (items.hasMore()) { Binding item = (Binding) items.next(); if (item.getObject() instanceof javax.naming.Context) { printResources (writer, prefix + item.getName() + "/", (javax.naming.Context) item.getObject(), type, clazz); } else { if ((clazz != null) && (!(clazz.isInstance(item.getObject())))) { continue; } writer.print(prefix + item.getName()); writer.print(':'); writer.print(item.getClassName()); // Do we want a description if available? writer.println(); } } } catch (Throwable t) { log("ManagerServlet.resources[" + type + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Render a list of security role names (and corresponding descriptions) * from the <code>org.apache.catalina.UserDatabase</code> resource that is * connected to the <code>users</code> resource reference. Typically, this * will be the global user database, but can be adjusted if you have * different user databases for different virtual hosts. * * @param writer Writer to render to */ protected void roles(PrintWriter writer) { if (debug >= 1) { log("roles: List security roles from user database"); } // Look up the UserDatabase instance we should use UserDatabase database = null; try { InitialContext ic = new InitialContext(); database = (UserDatabase) ic.lookup("java:comp/env/users"); } catch (NamingException e) { writer.println(sm.getString("managerServlet.userDatabaseError")); log("java:comp/env/users", e); return; } if (database == null) { writer.println(sm.getString("managerServlet.userDatabaseMissing")); return; } // Enumerate the available roles writer.println(sm.getString("managerServlet.rolesList")); Iterator roles = database.getRoles(); if (roles != null) { while (roles.hasNext()) { Role role = (Role) roles.next(); writer.print(role.getRolename()); writer.print(':'); if (role.getDescription() != null) { writer.print(role.getDescription()); } writer.println(); } } } /** * Writes System OS and JVM properties. * @param writer Writer to render to */ protected void serverinfo(PrintWriter writer) { if (debug >= 1) log("serverinfo"); try { StringBuffer props = new StringBuffer(); props.append("OK - Server info"); props.append("\nTomcat Version: "); props.append(ServerInfo.getServerInfo()); props.append("\nOS Name: "); props.append(System.getProperty("os.name")); props.append("\nOS Version: "); props.append(System.getProperty("os.version")); props.append("\nOS Architecture: "); props.append(System.getProperty("os.arch")); props.append("\nJVM Version: "); props.append(System.getProperty("java.runtime.version")); props.append("\nJVM Vendor: "); props.append(System.getProperty("java.vm.vendor")); writer.println(props.toString()); } catch (Throwable t) { getServletContext().log("ManagerServlet.serverinfo",t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Session information for the web application at the specified context path. * Displays a profile of session lastAccessedTime listing number * of sessions for each 10 minute interval up to 10 hours. * * @param writer Writer to render to * @param path Context path of the application to list session information for * @param idle Expire all sessions with idle time ≥ idle for this context */ protected void sessions(PrintWriter writer, String path, int idle) { if (debug >= 1) { log("sessions: Session information for web application at '" + path + "'"); if (idle >= 0) log("sessions: Session expiration for " + idle + " minutes '" + path + "'"); } if ((path == null) || (!path.startsWith("/") && path.equals(""))) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if( path.equals("/") ) path = ""; try { Context context = (Context) host.findChild(path); if (context == null) { writer.println(sm.getString("managerServlet.noContext", RequestUtil.filter(displayPath))); return; } Manager manager = context.getManager() ; if(manager == null) { writer.println(sm.getString("managerServlet.noManager", RequestUtil.filter(displayPath))); return; } int maxCount = 60; int maxInactiveInterval = context.getSessionTimeout(); int histoInterval = maxInactiveInterval / maxCount; if ( histoInterval * maxCount < maxInactiveInterval ) histoInterval++; if (0==histoInterval) histoInterval=1; maxCount = maxInactiveInterval / histoInterval; if ( histoInterval * maxCount < maxInactiveInterval ) maxCount++; writer.println(sm.getString("managerServlet.sessions", displayPath)); writer.println(sm.getString("managerServlet.sessiondefaultmax", "" + maxInactiveInterval)); Session [] sessions = manager.findSessions(); int [] timeout = new int[maxCount]; int notimeout = 0; int expired = 0; long now = System.currentTimeMillis(); for (int i = 0; i < sessions.length; i++) { int time = (int)((now-sessions[i].getLastAccessedTimeInternal())/1000); if (idle >= 0 && time >= idle*60) { sessions[i].expire(); expired++; } time=time/60/histoInterval; if (time < 0) notimeout++; else if (time >= maxCount) timeout[maxCount-1]++; else timeout[time]++; } if (timeout[0] > 0) writer.println(sm.getString("managerServlet.sessiontimeout", "<" + histoInterval, "" + timeout[0])); for (int i = 1; i < maxCount-1; i++) { if (timeout[i] > 0) writer.println(sm.getString("managerServlet.sessiontimeout", "" + (i)*histoInterval + " - <" + (i+1)*histoInterval, "" + timeout[i])); } if (timeout[maxCount-1] > 0) writer.println(sm.getString("managerServlet.sessiontimeout", ">=" + maxCount*histoInterval, "" + timeout[maxCount-1])); if (notimeout > 0) writer.println(sm.getString("managerServlet.sessiontimeout.unlimited", "" + notimeout)); if (idle >= 0) writer.println(sm.getString("managerServlet.sessiontimeout.expired", "" + idle,"" + expired)); } catch (Throwable t) { log("ManagerServlet.sessions[" + displayPath + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Session information for the web application at the specified context path. * Displays a profile of session lastAccessedTime listing number * of sessions for each 10 minute interval up to 10 hours. * * @param writer Writer to render to * @param path Context path of the application to list session information for */ protected void sessions(PrintWriter writer, String path) { sessions(writer, path, -1); } /** * * Extract the expiration request parameter * * @param path * @param req */ protected void expireSessions(PrintWriter writer, String path, HttpServletRequest req) { int idle = -1; String idleParam = req.getParameter("idle"); if (idleParam != null) { try { idle = Integer.parseInt(idleParam); } catch (NumberFormatException e) { log("Could not parse idle parameter to an int: " + idleParam); } } sessions(writer, path, idle); } /** * Start the web application at the specified context path. * * @param writer Writer to render to * @param path Context path of the application to be started */ protected void start(PrintWriter writer, String path) { if (debug >= 1) log("start: Starting web application at '" + path + "'"); if ((path == null) || (!path.startsWith("/") && path.equals(""))) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if( path.equals("/") ) path = ""; try { Context context = (Context) host.findChild(path); if (context == null) { writer.println(sm.getString("managerServlet.noContext", RequestUtil.filter(displayPath))); return; } ((Lifecycle) context).start(); if (context.getAvailable()) writer.println (sm.getString("managerServlet.started", displayPath)); else writer.println (sm.getString("managerServlet.startFailed", displayPath)); } catch (Throwable t) { getServletContext().log (sm.getString("managerServlet.startFailed", displayPath), t); writer.println (sm.getString("managerServlet.startFailed", displayPath)); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Stop the web application at the specified context path. * * @param writer Writer to render to * @param path Context path of the application to be stopped */ protected void stop(PrintWriter writer, String path) { if (debug >= 1) log("stop: Stopping web application at '" + path + "'"); if ((path == null) || (!path.startsWith("/") && path.equals(""))) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if( path.equals("/") ) path = ""; try { Context context = (Context) host.findChild(path); if (context == null) { writer.println(sm.getString("managerServlet.noContext", RequestUtil.filter(displayPath))); return; } // It isn't possible for the manager to stop itself if (context.getPath().equals(this.context.getPath())) { writer.println(sm.getString("managerServlet.noSelf")); return; } ((Lifecycle) context).stop(); writer.println(sm.getString("managerServlet.stopped", displayPath)); } catch (Throwable t) { log("ManagerServlet.stop[" + displayPath + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } /** * Undeploy the web application at the specified context path. * * @param writer Writer to render to * @param path Context path of the application to be removed */ protected void undeploy(PrintWriter writer, String path) { if (debug >= 1) log("undeploy: Undeploying web application at '" + path + "'"); if ((path == null) || (!path.startsWith("/") && path.equals(""))) { writer.println(sm.getString("managerServlet.invalidPath", RequestUtil.filter(path))); return; } String displayPath = path; if( path.equals("/") ) path = ""; try { // Validate the Context of the specified application Context context = (Context) host.findChild(path); if (context == null) { writer.println(sm.getString("managerServlet.noContext", RequestUtil.filter(displayPath))); return; } // Identify the appBase of the owning Host of this Context (if any) String appBase = null; File appBaseDir = null; if (context.getParent() instanceof Host) { appBase = ((Host) context.getParent()).getAppBase(); appBaseDir = new File(appBase); if (!appBaseDir.isAbsolute()) { appBaseDir = new File(System.getProperty("catalina.base"), appBase); } } if (!isDeployed(path)) { writer.println(sm.getString("managerServlet.notDeployed", RequestUtil.filter(displayPath))); return; } if (!isServiced(path)) { addServiced(path); try { // Try to stop the context first to be nicer ((Lifecycle) context).stop(); } catch (Throwable t) { // Ignore } try { if (path.lastIndexOf('/') > 0) { path = "/" + path.substring(1).replace('/','#'); } File war = new File(getAppBase(), getDocBase(path) + ".war"); File dir = new File(getAppBase(), getDocBase(path)); File xml = new File(configBase, getConfigFile(path) + ".xml"); if (war.exists()) { war.delete(); } else if (dir.exists()) { undeployDir(dir); } else { xml.delete(); } // Perform new deployment check(path.replace('#', '/')); } finally { removeServiced(path.replace('#','/')); } } writer.println(sm.getString("managerServlet.undeployed", displayPath)); } catch (Throwable t) { log("ManagerServlet.undeploy[" + displayPath + "]", t); writer.println(sm.getString("managerServlet.exception", t.toString())); } } // -------------------------------------------------------- Support Methods /** * Given a context path, get the config file name. */ protected String getConfigFile(String path) { String basename = null; if (path.equals("")) { basename = "ROOT"; } else { basename = path.substring(1).replace('/', '#'); } return (basename); } /** * Given a context path, get the doc base. */ protected String getDocBase(String path) { String basename = null; if (path.equals("")) { basename = "ROOT"; } else { basename = path.substring(1).replace('/', '#'); } return (basename); } /** * Return a File object representing the "application root" directory * for our associated Host. */ protected File getAppBase() { if (appBase != null) { return appBase; } File file = new File(host.getAppBase()); if (!file.isAbsolute()) file = new File(System.getProperty("catalina.base"), host.getAppBase()); try { appBase = file.getCanonicalFile(); } catch (IOException e) { appBase = file; } return (appBase); } /** * Invoke the isDeployed method on the deployer. */ protected boolean isDeployed(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; Boolean result = (Boolean) mBeanServer.invoke(oname, "isDeployed", params, signature); return result.booleanValue(); } /** * Invoke the check method on the deployer. */ protected void check(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; mBeanServer.invoke(oname, "check", params, signature); } /** * Invoke the isServiced method on the deployer. */ protected boolean isServiced(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; Boolean result = (Boolean) mBeanServer.invoke(oname, "isServiced", params, signature); return result.booleanValue(); } /** * Invoke the addServiced method on the deployer. */ protected void addServiced(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; mBeanServer.invoke(oname, "addServiced", params, signature); } /** * Invoke the removeServiced method on the deployer. */ protected void removeServiced(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; mBeanServer.invoke(oname, "removeServiced", params, signature); } /** * Delete the specified directory, including all of its contents and * subdirectories recursively. * * @param dir File object representing the directory to be deleted */ protected void undeployDir(File dir) { String files[] = dir.list(); if (files == null) { files = new String[0]; } for (int i = 0; i < files.length; i++) { File file = new File(dir, files[i]); if (file.isDirectory()) { undeployDir(file); } else { file.delete(); } } dir.delete(); } /** * Upload the WAR file included in this request, and store it at the * specified file location. * * @param request The servlet request we are processing * @param war The file into which we should store the uploaded WAR * * @exception IOException if an I/O error occurs during processing */ protected void uploadWar(HttpServletRequest request, File war) throws IOException { war.delete(); ServletInputStream istream = null; BufferedOutputStream ostream = null; try { istream = request.getInputStream(); ostream = new BufferedOutputStream(new FileOutputStream(war), 1024); byte buffer[] = new byte[1024]; while (true) { int n = istream.read(buffer); if (n < 0) { break; } ostream.write(buffer, 0, n); } ostream.flush(); ostream.close(); ostream = null; istream.close(); istream = null; } catch (IOException e) { war.delete(); throw e; } finally { if (ostream != null) { try { ostream.close(); } catch (Throwable t) { ; } ostream = null; } if (istream != null) { try { istream.close(); } catch (Throwable t) { ; } istream = null; } } } /** * Copy the specified file or directory to the destination. * * @param src File object representing the source * @param dest File object representing the destination */ public static boolean copy(File src, File dest) { boolean result = false; try { if( src != null && !src.getCanonicalPath().equals(dest.getCanonicalPath()) ) { result = copyInternal(src, dest, new byte[4096]); } } catch (IOException e) { e.printStackTrace(); } return result; } /** * Copy the specified file or directory to the destination. * * @param src File object representing the source * @param dest File object representing the destination */ public static boolean copyInternal(File src, File dest, byte[] buf) { boolean result = true; String files[] = null; if (src.isDirectory()) { files = src.list(); result = dest.mkdir(); } else { files = new String[1]; files[0] = ""; } if (files == null) { files = new String[0]; } for (int i = 0; (i < files.length) && result; i++) { File fileSrc = new File(src, files[i]); File fileDest = new File(dest, files[i]); if (fileSrc.isDirectory()) { result = copyInternal(fileSrc, fileDest, buf); } else { FileInputStream is = null; FileOutputStream os = null; try { is = new FileInputStream(fileSrc); os = new FileOutputStream(fileDest); int len = 0; while (true) { len = is.read(buf); if (len == -1) break; os.write(buf, 0, len); } } catch (IOException e) { e.printStackTrace(); result = false; } finally { if (is != null) { try { is.close(); } catch (IOException e) { } } if (os != null) { try { os.close(); } catch (IOException e) { } } } } } return result; } }