From b3a6a2aa30230a8b52f93ed8ef846ce55eaa6df7 Mon Sep 17 00:00:00 2001 From: remm Date: Thu, 8 Jun 2006 15:35:56 +0000 Subject: [PATCH] - Add the manager classes and a package renamed fileupload. - I think there should be basic multipart handling in the core, although maybe the manager doesn't belong here (so I would remove it later on). git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@412780 13f79535-47bb-0310-9956-ffa450edef68 --- java/org/apache/catalina/manager/Constants.java | 198 +++ .../catalina/manager/HTMLManagerServlet.java | 691 +++++++++ .../apache/catalina/manager/JMXProxyServlet.java | 230 +++ .../catalina/manager/LocalStrings.properties | 81 + .../catalina/manager/LocalStrings_de.properties | 78 + .../catalina/manager/LocalStrings_es.properties | 80 + .../catalina/manager/LocalStrings_fr.properties | 64 + .../catalina/manager/LocalStrings_ja.properties | 79 + .../apache/catalina/manager/ManagerServlet.java | 1582 ++++++++++++++++++++ .../catalina/manager/StatusManagerServlet.java | 358 +++++ .../apache/catalina/manager/StatusTransformer.java | 933 ++++++++++++ .../apache/catalina/manager/host/Constants.java | 203 +++ .../manager/host/HTMLHostManagerServlet.java | 478 ++++++ .../catalina/manager/host/HostManagerServlet.java | 684 +++++++++ .../catalina/manager/host/LocalStrings.properties | 59 + .../util/http/fileupload/DefaultFileItem.java | 607 ++++++++ .../http/fileupload/DefaultFileItemFactory.java | 189 +++ .../http/fileupload/DeferredFileOutputStream.java | 173 +++ .../util/http/fileupload/DiskFileUpload.java | 203 +++ .../tomcat/util/http/fileupload/FileItem.java | 230 +++ .../util/http/fileupload/FileItemFactory.java | 52 + .../tomcat/util/http/fileupload/FileUpload.java | 110 ++ .../util/http/fileupload/FileUploadBase.java | 640 ++++++++ .../util/http/fileupload/FileUploadException.java | 48 + .../util/http/fileupload/MultipartStream.java | 891 +++++++++++ .../http/fileupload/ThresholdingOutputStream.java | 249 +++ .../tomcat/util/http/fileupload/package.html | 66 + 27 files changed, 9256 insertions(+) create mode 100644 java/org/apache/catalina/manager/Constants.java create mode 100644 java/org/apache/catalina/manager/HTMLManagerServlet.java create mode 100644 java/org/apache/catalina/manager/JMXProxyServlet.java create mode 100644 java/org/apache/catalina/manager/LocalStrings.properties create mode 100644 java/org/apache/catalina/manager/LocalStrings_de.properties create mode 100644 java/org/apache/catalina/manager/LocalStrings_es.properties create mode 100644 java/org/apache/catalina/manager/LocalStrings_fr.properties create mode 100644 java/org/apache/catalina/manager/LocalStrings_ja.properties create mode 100644 java/org/apache/catalina/manager/ManagerServlet.java create mode 100644 java/org/apache/catalina/manager/StatusManagerServlet.java create mode 100644 java/org/apache/catalina/manager/StatusTransformer.java create mode 100644 java/org/apache/catalina/manager/host/Constants.java create mode 100644 java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java create mode 100644 java/org/apache/catalina/manager/host/HostManagerServlet.java create mode 100644 java/org/apache/catalina/manager/host/LocalStrings.properties create mode 100644 java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/FileItem.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/FileUpload.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/FileUploadException.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/MultipartStream.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java create mode 100644 java/org/apache/tomcat/util/http/fileupload/package.html diff --git a/java/org/apache/catalina/manager/Constants.java b/java/org/apache/catalina/manager/Constants.java new file mode 100644 index 000000000..124f7dca2 --- /dev/null +++ b/java/org/apache/catalina/manager/Constants.java @@ -0,0 +1,198 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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; + + +public class Constants { + + public static final String Package = "org.apache.catalina.manager"; + + public static final String HTML_HEADER_SECTION = + "\n" + + "\n" + + "\n"; + + public static final String BODY_HEADER_SECTION = + "{0}\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \"The\n" + + " \n" + + " \n" + + " \"The\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " {1}\n" + + "
\n" + + "
\n" + + "\n"; + + public static final String MESSAGE_SECTION = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
" + + "{0} 
{1}
\n" + + "
\n" + + "\n"; + + public static final String MANAGER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
{0}
{2}{4}{6}{8}
\n" + + "
\n" + + "\n"; + + public static final String SERVER_HEADER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + public static final String SERVER_ROW_SECTION = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "
{0}
{1}{2}{3}{4}{5}{6}
{0}{1}{2}{3}{4}{5}
\n" + + "
\n" + + "\n"; + + public static final String HTML_TAIL_SECTION = + "
\n" + + "
\n" + + " Copyright © 1999-2005, Apache Software Foundation" + + "
\n" + + "\n" + + "\n" + + ""; + public static final String CHARSET="utf-8"; + + public static final String XML_DECLARATION = + ""; + + public static final String XML_STYLE = + ""; + +} + diff --git a/java/org/apache/catalina/manager/HTMLManagerServlet.java b/java/org/apache/catalina/manager/HTMLManagerServlet.java new file mode 100644 index 000000000..ad84bb278 --- /dev/null +++ b/java/org/apache/catalina/manager/HTMLManagerServlet.java @@ -0,0 +1,691 @@ +/* + * Copyright 1999,2004-2005 The Apache Software Foundation. + * + * Licensed 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.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.Container; +import org.apache.catalina.Context; +import org.apache.catalina.util.RequestUtil; +import org.apache.catalina.util.ServerInfo; +import org.apache.tomcat.util.http.fileupload.DiskFileUpload; +import org.apache.tomcat.util.http.fileupload.FileItem; + +/** +* Servlet that enables remote management of the web applications deployed +* 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. +*

+* The difference between the ManagerServlet and this +* Servlet is that this Servlet prints out a HTML interface which +* makes it easier to administrate. +*

+* However if you use a software that parses the output of +* ManagerServletManagerServlet +* +* @author Bip Thelin +* @author Malcolm Edgar +* @author Glenn L. Nielsen +* @version $Revision: 326772 $, $Date: 2005-10-20 03:37:02 +0200 (jeu., 20 oct. 2005) $ +* @see ManagerServlet +*/ + +public final class HTMLManagerServlet extends ManagerServlet { + + // --------------------------------------------------------- Public Methods + + /** + * 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 { + + // Identify the request parameters that we need + String command = request.getPathInfo(); + + String path = request.getParameter("path"); + String deployPath = request.getParameter("deployPath"); + String deployConfig = request.getParameter("deployConfig"); + String deployWar = request.getParameter("deployWar"); + + // Prepare our output writer to generate the response message + response.setContentType("text/html; charset=" + Constants.CHARSET); + + String message = ""; + // Process the requested command + if (command == null || command.equals("/")) { + } else if (command.equals("/deploy")) { + message = deployInternal(deployConfig, deployPath, deployWar); + } else if (command.equals("/list")) { + } else if (command.equals("/reload")) { + message = reload(path); + } else if (command.equals("/undeploy")) { + message = undeploy(path); + } else if (command.equals("/sessions")) { + message = sessions(path); + } else if (command.equals("/start")) { + message = start(path); + } else if (command.equals("/stop")) { + message = stop(path); + } else { + message = + sm.getString("managerServlet.unknownCommand", + RequestUtil.filter(command)); + } + + list(request, response, message); + } + + /** + * Process a POST 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 doPost(HttpServletRequest request, + HttpServletResponse response) + throws IOException, ServletException { + + // Identify the request parameters that we need + String command = request.getPathInfo(); + + if (command == null || !command.equals("/upload")) { + doGet(request,response); + return; + } + + // Prepare our output writer to generate the response message + response.setContentType("text/html; charset=" + Constants.CHARSET); + + String message = ""; + + // Create a new file upload handler + DiskFileUpload upload = new DiskFileUpload(); + + // Get the tempdir + File tempdir = (File) getServletContext().getAttribute + ("javax.servlet.context.tempdir"); + // Set upload parameters + upload.setSizeMax(-1); + upload.setRepositoryPath(tempdir.getCanonicalPath()); + + // Parse the request + String basename = null; + String war = null; + FileItem warUpload = null; + try { + List items = upload.parseRequest(request); + + // Process the uploaded fields + Iterator iter = items.iterator(); + while (iter.hasNext()) { + FileItem item = (FileItem) iter.next(); + + if (!item.isFormField()) { + if (item.getFieldName().equals("deployWar") && + warUpload == null) { + warUpload = item; + } else { + item.delete(); + } + } + } + while (true) { + if (warUpload == null) { + message = sm.getString + ("htmlManagerServlet.deployUploadNoFile"); + break; + } + war = warUpload.getName(); + if (!war.toLowerCase().endsWith(".war")) { + message = sm.getString + ("htmlManagerServlet.deployUploadNotWar",war); + break; + } + // Get the filename if uploaded name includes a path + if (war.lastIndexOf('\\') >= 0) { + war = war.substring(war.lastIndexOf('\\') + 1); + } + if (war.lastIndexOf('/') >= 0) { + war = war.substring(war.lastIndexOf('/') + 1); + } + // Identify the appBase of the owning Host of this Context + // (if any) + basename = war.substring(0, war.toLowerCase().indexOf(".war")); + File file = new File(getAppBase(), war); + if (file.exists()) { + message = sm.getString + ("htmlManagerServlet.deployUploadWarExists",war); + break; + } + String path = null; + if (basename.equals("ROOT")) { + path = ""; + } else { + path = "/" + basename; + } + + if (!isServiced(path)) { + addServiced(path); + try { + warUpload.write(file); + // Perform new deployment + check(path); + } finally { + removeServiced(path); + } + } + break; + } + } catch(Exception e) { + message = sm.getString + ("htmlManagerServlet.deployUploadFail", e.getMessage()); + log(message, e); + } finally { + if (warUpload != null) { + warUpload.delete(); + } + warUpload = null; + } + + list(request, response, message); + } + + /** + * Deploy an application for the specified path from the specified + * web application archive. + * + * @param config URL of the context configuration file to be deployed + * @param path Context path of the application to be deployed + * @param war URL of the web application archive to be deployed + * @return message String + */ + protected String deployInternal(String config, String path, String war) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.deploy(printWriter, config, path, war, false); + + return stringWriter.toString(); + } + + /** + * Render a HTML list of the currently active Contexts in our virtual host, + * and memory and server status information. + * + * @param request The request + * @param response The response + * @param message a message to display + */ + public void list(HttpServletRequest request, + HttpServletResponse response, + String message) throws IOException { + + if (debug >= 1) + log("list: Listing contexts for virtual host '" + + host.getName() + "'"); + + PrintWriter writer = response.getWriter(); + + // HTML Header Section + writer.print(Constants.HTML_HEADER_SECTION); + + // Body Header Section + Object[] args = new Object[2]; + args[0] = request.getContextPath(); + args[1] = sm.getString("htmlManagerServlet.title"); + writer.print(MessageFormat.format + (Constants.BODY_HEADER_SECTION, args)); + + // Message Section + args = new Object[3]; + args[0] = sm.getString("htmlManagerServlet.messageLabel"); + args[1] = (message == null || message.length() == 0) ? "OK" : message; + writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args)); + + // Manager Section + args = new Object[9]; + args[0] = sm.getString("htmlManagerServlet.manager"); + args[1] = response.encodeURL(request.getContextPath() + "/html/list"); + args[2] = sm.getString("htmlManagerServlet.list"); + args[3] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlManagerServlet.helpHtmlManagerFile")); + args[4] = sm.getString("htmlManagerServlet.helpHtmlManager"); + args[5] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlManagerServlet.helpManagerFile")); + args[6] = sm.getString("htmlManagerServlet.helpManager"); + args[7] = response.encodeURL + (request.getContextPath() + "/status"); + args[8] = sm.getString("statusServlet.title"); + writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args)); + + // Apps Header Section + args = new Object[6]; + args[0] = sm.getString("htmlManagerServlet.appsTitle"); + args[1] = sm.getString("htmlManagerServlet.appsPath"); + args[2] = sm.getString("htmlManagerServlet.appsName"); + args[3] = sm.getString("htmlManagerServlet.appsAvailable"); + args[4] = sm.getString("htmlManagerServlet.appsSessions"); + args[5] = sm.getString("htmlManagerServlet.appsTasks"); + writer.print(MessageFormat.format(APPS_HEADER_SECTION, args)); + + // Apps Row Section + // Create sorted map of deployed applications context paths. + Container children[] = host.findChildren(); + String contextPaths[] = new String[children.length]; + for (int i = 0; i < children.length; i++) + contextPaths[i] = children[i].getName(); + + TreeMap sortedContextPathsMap = new TreeMap(); + + for (int i = 0; i < contextPaths.length; i++) { + String displayPath = contextPaths[i]; + sortedContextPathsMap.put(displayPath, contextPaths[i]); + } + + String appsStart = sm.getString("htmlManagerServlet.appsStart"); + String appsStop = sm.getString("htmlManagerServlet.appsStop"); + String appsReload = sm.getString("htmlManagerServlet.appsReload"); + String appsUndeploy = sm.getString("htmlManagerServlet.appsUndeploy"); + + Iterator iterator = sortedContextPathsMap.entrySet().iterator(); + boolean isHighlighted = true; + String highlightColor = null; + + while (iterator.hasNext()) { + // Bugzilla 34818, alternating row colors + isHighlighted = !isHighlighted; + if(isHighlighted) { + highlightColor = "#C3F3C3"; + } else { + highlightColor = "#FFFFFF"; + } + + Map.Entry entry = (Map.Entry) iterator.next(); + String displayPath = (String) entry.getKey(); + String contextPath = (String) entry.getKey(); + Context context = (Context) host.findChild(contextPath); + if (displayPath.equals("")) { + displayPath = "/"; + } + + if (context != null ) { + args = new Object[6]; + args[0] = displayPath; + args[1] = context.getDisplayName(); + if (args[1] == null) { + args[1] = " "; + } + args[2] = new Boolean(context.getAvailable()); + args[3] = response.encodeURL + (request.getContextPath() + + "/html/sessions?path=" + displayPath); + if (context.getManager() != null) { + args[4] = new Integer + (context.getManager().getActiveSessions()); + } else { + args[4] = new Integer(0); + } + + args[5] = highlightColor; + + writer.print + (MessageFormat.format(APPS_ROW_DETAILS_SECTION, args)); + + args = new Object[9]; + args[0] = response.encodeURL + (request.getContextPath() + + "/html/start?path=" + displayPath); + args[1] = appsStart; + args[2] = response.encodeURL + (request.getContextPath() + + "/html/stop?path=" + displayPath); + args[3] = appsStop; + args[4] = response.encodeURL + (request.getContextPath() + + "/html/reload?path=" + displayPath); + args[5] = appsReload; + args[6] = response.encodeURL + (request.getContextPath() + + "/html/undeploy?path=" + displayPath); + args[7] = appsUndeploy; + + args[8] = highlightColor; + + if (context.getPath().equals(this.context.getPath())) { + writer.print(MessageFormat.format( + MANAGER_APP_ROW_BUTTON_SECTION, args)); + } else if (context.getAvailable()) { + writer.print(MessageFormat.format( + STARTED_APPS_ROW_BUTTON_SECTION, args)); + } else { + writer.print(MessageFormat.format( + STOPPED_APPS_ROW_BUTTON_SECTION, args)); + } + + } + } + + // Deploy Section + args = new Object[7]; + args[0] = sm.getString("htmlManagerServlet.deployTitle"); + args[1] = sm.getString("htmlManagerServlet.deployServer"); + args[2] = response.encodeURL(request.getContextPath() + "/html/deploy"); + args[3] = sm.getString("htmlManagerServlet.deployPath"); + args[4] = sm.getString("htmlManagerServlet.deployConfig"); + args[5] = sm.getString("htmlManagerServlet.deployWar"); + args[6] = sm.getString("htmlManagerServlet.deployButton"); + writer.print(MessageFormat.format(DEPLOY_SECTION, args)); + + args = new Object[4]; + args[0] = sm.getString("htmlManagerServlet.deployUpload"); + args[1] = response.encodeURL(request.getContextPath() + "/html/upload"); + args[2] = sm.getString("htmlManagerServlet.deployUploadFile"); + args[3] = sm.getString("htmlManagerServlet.deployButton"); + writer.print(MessageFormat.format(UPLOAD_SECTION, args)); + + // Server Header Section + args = new Object[7]; + args[0] = sm.getString("htmlManagerServlet.serverTitle"); + args[1] = sm.getString("htmlManagerServlet.serverVersion"); + args[2] = sm.getString("htmlManagerServlet.serverJVMVersion"); + args[3] = sm.getString("htmlManagerServlet.serverJVMVendor"); + args[4] = sm.getString("htmlManagerServlet.serverOSName"); + args[5] = sm.getString("htmlManagerServlet.serverOSVersion"); + args[6] = sm.getString("htmlManagerServlet.serverOSArch"); + writer.print(MessageFormat.format + (Constants.SERVER_HEADER_SECTION, args)); + + // Server Row Section + args = new Object[6]; + args[0] = ServerInfo.getServerInfo(); + args[1] = System.getProperty("java.runtime.version"); + args[2] = System.getProperty("java.vm.vendor"); + args[3] = System.getProperty("os.name"); + args[4] = System.getProperty("os.version"); + args[5] = System.getProperty("os.arch"); + writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args)); + + // HTML Tail Section + writer.print(Constants.HTML_TAIL_SECTION); + + // Finish up the response + writer.flush(); + writer.close(); + } + + /** + * Reload the web application at the specified context path. + * + * @see ManagerServlet#reload(PrintWriter, String) + * + * @param path Context path of the application to be restarted + * @return message String + */ + protected String reload(String path) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.reload(printWriter, path); + + return stringWriter.toString(); + } + + /** + * Undeploy the web application at the specified context path. + * + * @see ManagerServlet#undeploy(PrintWriter, String) + * + * @param path Context path of the application to be undeployd + * @return message String + */ + protected String undeploy(String path) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.undeploy(printWriter, path); + + return stringWriter.toString(); + } + + /** + * Display session information and invoke list. + * + * @see ManagerServlet#sessions(PrintWriter, String) + * + * @param path Context path of the application to list session information + * @return message String + */ + public String sessions(String path) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.sessions(printWriter, path); + + return stringWriter.toString(); + } + + /** + * Start the web application at the specified context path. + * + * @see ManagerServlet#start(PrintWriter, String) + * + * @param path Context path of the application to be started + * @return message String + */ + public String start(String path) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.start(printWriter, path); + + return stringWriter.toString(); + } + + /** + * Stop the web application at the specified context path. + * + * @see ManagerServlet#stop(PrintWriter, String) + * + * @param path Context path of the application to be stopped + * @return message String + */ + protected String stop(String path) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.stop(printWriter, path); + + return stringWriter.toString(); + } + + // ------------------------------------------------------ Private Constants + + // These HTML sections are broken in relatively small sections, because of + // limited number of subsitutions MessageFormat can process + // (maximium of 10). + + private static final String APPS_HEADER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private static final String APPS_ROW_DETAILS_SECTION = + "\n" + + " \n" + + " \n" + + " \n" + + " \n"; + + private static final String MANAGER_APP_ROW_BUTTON_SECTION = + " \n" + + "\n"; + + private static final String STARTED_APPS_ROW_BUTTON_SECTION = + " \n" + + "\n"; + + private static final String STOPPED_APPS_ROW_BUTTON_SECTION = + " \n" + + "\n"; + + private static final String DEPLOY_SECTION = + "
{0}
{1}{2}{3}{4}{5}
{0}{1}{2}{4}\n" + + " \n" + + "  {1} \n" + + "  {3} \n" + + "  {5} \n" + + "  {7} \n" + + " \n" + + "
\n" + + " \n" + + "  {1} \n" + + "  {3} \n" + + "  {5} \n" + + "  {7} \n" + + " \n" + + "
\n" + + " \n" + + "  {1} \n" + + "  {3} \n" + + "  {5} \n" + + "  {7} \n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + "\n"; + + private static final String UPLOAD_SECTION = + "\n" + + " \n" + + "\n" + + "\n" + + "
{0}
{1}
\n" + + "
\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "
\n" + + " {3}\n" + + " \n" + + " \n" + + "
\n" + + " {4}\n" + + " \n" + + " \n" + + "
\n" + + " {5}\n" + + " \n" + + " \n" + + "
\n" + + "  \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
{0}
\n" + + "
\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "
\n" + + " {2}\n" + + " \n" + + " \n" + + "
\n" + + "  \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n"; + +} diff --git a/java/org/apache/catalina/manager/JMXProxyServlet.java b/java/org/apache/catalina/manager/JMXProxyServlet.java new file mode 100644 index 000000000..7509ed85f --- /dev/null +++ b/java/org/apache/catalina/manager/JMXProxyServlet.java @@ -0,0 +1,230 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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.IOException; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.MBeanInfo; +import javax.management.MBeanAttributeInfo; +import javax.management.Attribute; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tomcat.util.modeler.Registry; + +/** + * This servlet will dump JMX attributes in a simple format + * and implement proxy services for modeler. + * + * @author Costin Manolache + */ +public class JMXProxyServlet extends HttpServlet { + // ----------------------------------------------------- Instance Variables + + /** + * MBean server. + */ + protected MBeanServer mBeanServer = null; + protected Registry registry; + // --------------------------------------------------------- Public Methods + + + /** + * Initialize this servlet. + */ + public void init() throws ServletException { + // Retrieve the MBean server + registry = Registry.getRegistry(null, null); + mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); + } + + + /** + * 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 + { + + response.setContentType("text/plain"); + + PrintWriter writer = response.getWriter(); + String qryString= request.getQueryString(); + + if( mBeanServer==null ) { + writer.println("Error - No mbean server"); + return; + } + + String qry=request.getParameter("set"); + if( qry!= null ) { + String name=request.getParameter("att"); + String val=request.getParameter("val"); + + setAttribute( writer, qry, name, val ); + return; + } + qry=request.getParameter("get"); + if( qry!= null ) { + String name=request.getParameter("att"); + getAttribute( writer, qry, name ); + return; + } + qry=request.getParameter("qry"); + if( qry == null ) { + qry = "*:*"; + } + + listBeans( writer, qry ); + + } + + public void getAttribute(PrintWriter writer, String onameStr, String att) { + try { + ObjectName oname = new ObjectName(onameStr); + Object value = mBeanServer.getAttribute(oname, att); + writer.println("OK - Attribute get '" + onameStr + "' - " + att + "= " + value.toString() ); + } catch (Exception ex) { + writer.println("Error - " + ex.toString()); + } + } + + public void setAttribute( PrintWriter writer, + String onameStr, String att, String val ) + { + try { + ObjectName oname=new ObjectName( onameStr ); + String type=registry.getType(oname, att); + Object valueObj=registry.convertValue(type, val ); + mBeanServer.setAttribute( oname, new Attribute(att, valueObj)); + writer.println("OK - Attribute set"); + } catch( Exception ex ) { + writer.println("Error - " + ex.toString()); + } + } + + public void listBeans( PrintWriter writer, String qry ) + { + + Set names = null; + try { + names=mBeanServer.queryNames(new ObjectName(qry), null); + writer.println("OK - Number of results: " + names.size()); + writer.println(); + } catch (Exception e) { + writer.println("Error - " + e.toString()); + return; + } + + Iterator it=names.iterator(); + while( it.hasNext()) { + ObjectName oname=(ObjectName)it.next(); + writer.println( "Name: " + oname.toString()); + + try { + MBeanInfo minfo=mBeanServer.getMBeanInfo(oname); + // can't be null - I thinl + String code=minfo.getClassName(); + if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) { + code=(String)mBeanServer.getAttribute(oname, "modelerType"); + } + writer.println("modelerType: " + code); + + MBeanAttributeInfo attrs[]=minfo.getAttributes(); + Object value=null; + + for( int i=0; i< attrs.length; i++ ) { + if( ! attrs[i].isReadable() ) continue; + if( ! isSupported( attrs[i].getType() )) continue; + String attName=attrs[i].getName(); + if( attName.indexOf( "=") >=0 || + attName.indexOf( ":") >=0 || + attName.indexOf( " ") >=0 ) { + continue; + } + + try { + value=mBeanServer.getAttribute(oname, attName); + } catch( Throwable t) { + System.out.println("Error getting attribute " + oname + + " " + attName + " " + t.toString()); + continue; + } + if( value==null ) continue; + if( "modelerType".equals( attName)) continue; + String valueString=value.toString(); + writer.println( attName + ": " + escape(valueString)); + } + } catch (Exception e) { + // Ignore + } + writer.println(); + } + + } + + public String escape(String value) { + // The only invalid char is \n + // We also need to keep the string short and split it with \nSPACE + // XXX TODO + int idx=value.indexOf( "\n" ); + if( idx < 0 ) return value; + + int prev=0; + StringBuffer sb=new StringBuffer(); + while( idx >= 0 ) { + appendHead(sb, value, prev, idx-1); + + sb.append( "\\n\n "); + prev=idx+1; + if( idx==value.length() -1 ) break; + idx=value.indexOf('\n', idx+1); + } + if( prev < value.length() ) + appendHead( sb, value, prev, value.length()); + return sb.toString(); + } + + private void appendHead( StringBuffer sb, String value, int start, int end) { + int pos=start; + while( end-pos > 78 ) { + sb.append( value.substring(pos, pos+78)); + sb.append( "\n "); + pos=pos+78; + } + sb.append( value.substring(pos,end)); + } + + public boolean isSupported( String type ) { + return true; + } +} diff --git a/java/org/apache/catalina/manager/LocalStrings.properties b/java/org/apache/catalina/manager/LocalStrings.properties new file mode 100644 index 000000000..feeabb131 --- /dev/null +++ b/java/org/apache/catalina/manager/LocalStrings.properties @@ -0,0 +1,81 @@ +htmlManagerServlet.appsAvailable=Running +htmlManagerServlet.appsName=Display Name +htmlManagerServlet.appsPath=Path +htmlManagerServlet.appsReload=Reload +htmlManagerServlet.appsUndeploy=Undeploy +htmlManagerServlet.appsSessions=Sessions +htmlManagerServlet.appsStart=Start +htmlManagerServlet.appsStop=Stop +htmlManagerServlet.appsTasks=Commands +htmlManagerServlet.appsTitle=Applications +htmlManagerServlet.helpHtmlManager=HTML Manager Help +htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html +htmlManagerServlet.helpManager=Manager Help +htmlManagerServlet.helpManagerFile=manager-howto.html +htmlManagerServlet.deployButton=Deploy +htmlManagerServlet.deployConfig=XML Configuration file URL: +htmlManagerServlet.deployPath=Context Path (optional): +htmlManagerServlet.deployServer=Deploy directory or WAR file located on server +htmlManagerServlet.deployTitle=Deploy +htmlManagerServlet.deployUpload=WAR file to deploy +htmlManagerServlet.deployUploadFail=FAIL - Deploy Upload Failed, Exception: {0} +htmlManagerServlet.deployUploadFile=Select WAR file to upload +htmlManagerServlet.deployUploadNotWar=FAIL - File uploaded \"{0}\" must be a .war +htmlManagerServlet.deployUploadNoFile=FAIL - File upload failed, no file +htmlManagerServlet.deployUploadWarExists=FAIL - War file \"{0}\" already exists on server +htmlManagerServlet.deployWar=WAR or Directory URL: +htmlManagerServlet.list=List Applications +htmlManagerServlet.manager=Manager +htmlManagerServlet.messageLabel=Message: +htmlManagerServlet.serverJVMVendor=JVM Vendor +htmlManagerServlet.serverJVMVersion=JVM Version +htmlManagerServlet.serverOSArch=OS Architecture +htmlManagerServlet.serverOSName=OS Name +htmlManagerServlet.serverOSVersion=OS Version +htmlManagerServlet.serverTitle=Server Information +htmlManagerServlet.serverVersion=Tomcat Version +htmlManagerServlet.title=Tomcat Web Application Manager +managerServlet.alreadyContext=FAIL - Application already exists at path {0} +managerServlet.alreadyDocBase=FAIL - Directory {0} is already in use +managerServlet.cannotInvoke=Cannot invoke manager servlet through invoker +managerServlet.configured=OK - Deployed application from context file {0} +managerServlet.deployed=OK - Deployed application at context path {0} +managerServlet.deployFailed=FAIL - Failed to deploy application at context path {0} +managerServlet.exception=FAIL - Encountered exception {0} +managerServlet.deployed=OK - Deployed application at context path {0} +managerServlet.invalidPath=FAIL - Invalid context path {0} was specified +managerServlet.invalidWar=FAIL - Invalid application URL {0} was specified +managerServlet.listed=OK - Listed applications for virtual host {0} +managerServlet.listitem={0}:{1}:{2}:{3} +managerServlet.noAppBase=FAIL - Cannot identify application base for context path {0} +managerServlet.noCommand=FAIL - No command was specified +managerServlet.noContext=FAIL - No context exists for path {0} +managerServlet.noDirectory=FAIL - Non-directory document base for path {0} +managerServlet.noDocBase=FAIL - Cannot undeploy document base for path {0} +managerServlet.noGlobal=FAIL - No global JNDI resources are available +managerServlet.noReload=FAIL - Reload not supported on WAR deployed at path {0} +managerServlet.noRename=FAIL - Cannot deploy uploaded WAR for path {0} +managerServlet.noRole=FAIL - User does not possess role {0} +managerServlet.noSelf=FAIL - The manager can not reload, undeploy, stop, or undeploy itself +managerServlet.noWrapper=Container has not called setWrapper() for this servlet +managerServlet.reloaded=OK - Reloaded application at context path {0} +managerServlet.undeployd=OK - Undeployed application at context path {0} +managerServlet.resourcesAll=OK - Listed global resources of all types +managerServlet.resourcesType=OK - Listed global resources of type {0} +managerServlet.rolesList=OK - Listed security roles +managerServlet.saveFail=FAIL - Configuration save failed: {0} +managerServlet.saved=OK - Server configuration saved +managerServlet.savedContext=OK - Context {0} configuration saved +managerServlet.sessiondefaultmax=Default maximum session inactive interval {0} minutes +managerServlet.sessiontimeout={0} minutes:{1} sessions +managerServlet.sessions=OK - Session information for application at context path {0} +managerServlet.started=OK - Started application at context path {0} +managerServlet.startFailed=FAIL - Application at context path {0} could not be started +managerServlet.stopped=OK - Stopped application at context path {0} +managerServlet.undeployed=OK - Undeployed application at context path {0} +managerServlet.unknownCommand=FAIL - Unknown command {0} +managerServlet.userDatabaseError=FAIL - Cannot resolve user database reference +managerServlet.userDatabaseMissing=FAIL - No user database is available + +statusServlet.title=Server Status +statusServlet.complete=Complete Server Status diff --git a/java/org/apache/catalina/manager/LocalStrings_de.properties b/java/org/apache/catalina/manager/LocalStrings_de.properties new file mode 100644 index 000000000..20ada40c2 --- /dev/null +++ b/java/org/apache/catalina/manager/LocalStrings_de.properties @@ -0,0 +1,78 @@ +htmlManagerServlet.appsAvailable=Verfügbar +htmlManagerServlet.appsName=Anzeigename +htmlManagerServlet.appsPath=Kontext Pfad +htmlManagerServlet.appsReload=Neu laden +htmlManagerServlet.appsUndeploy=Entfernen +htmlManagerServlet.appsSessions=Sitzungen +htmlManagerServlet.appsStart=Start +htmlManagerServlet.appsStop=Stop +htmlManagerServlet.appsTasks=Kommandos +htmlManagerServlet.appsTitle=Anwendungen +htmlManagerServlet.helpHtmlManager=Hilfeseite HTML Manager (englisch) +htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html +htmlManagerServlet.helpManager=Hilfeseite Manager (englisch) +htmlManagerServlet.helpManagerFile=manager-howto.html +htmlManagerServlet.deployButton=Installieren +htmlManagerServlet.deployConfig=XML Konfigurationsdatei URL: +htmlManagerServlet.deployPath=Kontext Pfad (optional): +htmlManagerServlet.deployServer=Verzeichnis oder WAR Datei auf Server installieren +htmlManagerServlet.deployTitle=Installieren +htmlManagerServlet.deployUpload=Lokale WAR Datei zur Installation hochladen +htmlManagerServlet.deployUploadFail=FEHLER - Hochladen zur Installation fehlgeschlagen, Ausnahme: {0} +htmlManagerServlet.deployUploadFile=WAR Datei auswählen +htmlManagerServlet.deployUploadNotWar=FEHLER - Hochgeladene Datei \"{0}\" muss ein .war sein +htmlManagerServlet.deployUploadNoFile=FEHLER - Hochladen fehlgeschlagen, keine Datei vorhanden +htmlManagerServlet.deployUploadWarExists=FEHLER - WAR Datei \"{0}\" existiert bereits auf Server +htmlManagerServlet.deployWar=WAR oder Verzeichnis URL: +htmlManagerServlet.list=Anwendungen auflisten +htmlManagerServlet.manager=Manager +htmlManagerServlet.messageLabel=Nachricht: +htmlManagerServlet.serverJVMVendor=JVM Hersteller +htmlManagerServlet.serverJVMVersion=JVM Version +htmlManagerServlet.serverOSArch=OS Architektur +htmlManagerServlet.serverOSName=OS Name +htmlManagerServlet.serverOSVersion=OS Version +htmlManagerServlet.serverTitle=Server Informationen +htmlManagerServlet.serverVersion=Tomcat Version +htmlManagerServlet.title=Tomcat Webanwendungs-Manager +managerServlet.alreadyContext=FEHLER - Anwendung existiert bereits für Kontext Pfad {0} +managerServlet.alreadyDocBase=FEHLER - Verzeichnis {0} bereits in Benutzung +managerServlet.cannotInvoke=Kann Manager-Servlet nicht durch Invoker aufrufen +managerServlet.configured=OK - Anwendung von Kontext-Datei {0} installiert +managerServlet.deployed=OK - Anwendung mit Kontext Pfad {0} installiert +managerServlet.exception=FEHLER - Ausnahme aufgetreten {0} +managerServlet.deployed=OK - Anwendung mit Kontext Pfad {0} installiert +managerServlet.invalidPath=FEHLER - Ungültiger Kontext Pfad {0} angegeben +managerServlet.invalidWar=FEHLER - Ungültige URL {0} für Anwendung angegeben +managerServlet.listed=OK - Auflistung der Webanwendungen für virtuellen Server {0} +managerServlet.listitem={0}:{1}:{2}:{3} +managerServlet.noAppBase=FEHLER - Kann Verzeichnis für Kontext Pfad {0} nicht finden +managerServlet.noCommand=FEHLER - Es wurde kein Kommando angegeben +managerServlet.noContext=FEHLER - Es existiert kein Kontext für Pfad {0} +managerServlet.noDirectory=FEHLER - Pfad {0} ist kein Verzeichnis +managerServlet.noDocBase=FEHLER - Kann Webanwendungs-Verzeichnis nicht entfernen für Kontext Pfad {0} +managerServlet.noGlobal=FEHLER - Keine globalen JNDI Ressourcen verfügbar +managerServlet.noReload=FEHLER - Neu laden nicht unterstützt für WAR mit Pfad {0} +managerServlet.noRename=FEHLER - Kann hochgeladenes WAR mit Pfad {0} nicht installieren +managerServlet.noRole=FEHLER - Benutzer nicht in Rolle {0} +managerServlet.noSelf=FEHLER - Manager-Kommandos können nicht auf die Manager-Anwendung selbst angewendet werden +managerServlet.noWrapper=Container hat setWrapper() für dieses Servlet nicht aufgerufen +managerServlet.reloaded=OK - Anwendung mit Kontext Pfad {0} neu geladen +managerServlet.undeployd=OK - Anwendung mit Kontext Pfad {0} entfernt +managerServlet.resourcesAll=OK - Auflistung globaler Ressourcen (alle Typen) +managerServlet.resourcesType=OK - Auflistung globaler Ressourcen von Typ {0} +managerServlet.rolesList=OK - Auflistung der Sicherheits-Rollen +managerServlet.saveFail=FEHLER - Speichern der Konfiguration fehlgeschlagen: {0} +managerServlet.sessiondefaultmax=Voreingestellter Sitzungsablauf nach maximal {0} Minuten Inaktivität +managerServlet.sessiontimeout={0} Minuten: {1} Sitzungen +managerServlet.sessions=OK - Sitzungs-Informationen für Anwendung mit Kontext Pfad {0} +managerServlet.started=OK - Anwendung mit Kontext Pfad {0} gestartet +managerServlet.startFailed=FEHLER - Anwendung mit Kontext Pfad {0} konnte nicht gestartet werden +managerServlet.stopped=OK - Anwendung mit Kontext Pfad {0} gestoppt +managerServlet.undeployed=OK - Anwendung mit Kontext Pfad {0} entfernt +managerServlet.unknownCommand=FEHLER - Unbekanntes Kommando {0} +managerServlet.userDatabaseError=FEHLER - Kann Referenz auf Benutzerdatendank nicht auflösen +managerServlet.userDatabaseMissing=FEHLER - Keine Benutzerdatenbank vorhanden + +statusServlet.title=Server Status +statusServlet.complete=Ausführlicher Server Status diff --git a/java/org/apache/catalina/manager/LocalStrings_es.properties b/java/org/apache/catalina/manager/LocalStrings_es.properties new file mode 100644 index 000000000..72a2c1b09 --- /dev/null +++ b/java/org/apache/catalina/manager/LocalStrings_es.properties @@ -0,0 +1,80 @@ +htmlManagerServlet.appsAvailable=Ejecutándose +htmlManagerServlet.appsName=Nombre a Mostrar +htmlManagerServlet.appsPath=Trayectoria +htmlManagerServlet.appsReload=Recargar +htmlManagerServlet.appsUndeploy=Replegar +htmlManagerServlet.appsSessions=Sesiones +htmlManagerServlet.appsStart=Arrancar +htmlManagerServlet.appsStop=Parar +htmlManagerServlet.appsTasks=Comandos +htmlManagerServlet.appsTitle=Aplicaciones +htmlManagerServlet.helpHtmlManager=Ayuda HTML de Gestor +htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html +htmlManagerServlet.helpManager=Ayuda de Gestor +htmlManagerServlet.helpManagerFile=manager-howto.html +htmlManagerServlet.deployButton=Desplegar +htmlManagerServlet.deployConfig=URL de archivo de Configuración XML: +htmlManagerServlet.deployPath=Trayectoria de Contexto (opcional): +htmlManagerServlet.deployServer=Desplegar directorio o archivo WAR localizado en servidor +htmlManagerServlet.deployTitle=Desplegar +htmlManagerServlet.deployUpload=Archivo WAR a desplegar +htmlManagerServlet.deployUploadFail=FALLO - Falló Carga de Despliegue, Excepción: {0} +htmlManagerServlet.deployUploadFile=Seleccione archivo WAR a cargar +htmlManagerServlet.deployUploadNotWar=FALLO - El archivo cargado \"{0}\" debe de ser un .war +htmlManagerServlet.deployUploadNoFile=FALLO - Falló la carga de archivo, no hay archivo +htmlManagerServlet.deployUploadWarExists=FALLO - El archivo war \"{0}\" ya existe en el servidor +htmlManagerServlet.deployWar=URL de WAR o Directorio: +htmlManagerServlet.list=Listar Aplicaciones +htmlManagerServlet.manager=Gestor +htmlManagerServlet.messageLabel=Mensaje: +htmlManagerServlet.serverJVMVendor=Vendedor JVM +htmlManagerServlet.serverJVMVersion=Versión JVM +htmlManagerServlet.serverOSArch=Arquitectura de SO +htmlManagerServlet.serverOSName=Nombre de SO +htmlManagerServlet.serverOSVersion=Versión de SO +htmlManagerServlet.serverTitle=Información de Servidor +htmlManagerServlet.serverVersion=Versión de Tomcat +htmlManagerServlet.title=Gestor de Aplicaciones Web de Tomcat +managerServlet.alreadyContext=FALLO - Ya existe la aplicación en la trayectoria {0} +managerServlet.alreadyDocBase=FALLO - Directorio {0} ya está siendo usado +managerServlet.cannotInvoke=No puedo invocar servlet de gestor a través de invocador +managerServlet.configured=OK - Desplegada aplicación desde archivo de contexto {0} +managerServlet.deployed=OK - Desplegada aplicación en trayectoria de contexto {0} +managerServlet.exception=FALLO - Encontrada excepción {0} +managerServlet.deployed=OK - Desplegada aplicación en trayectoria de contexto {0} +managerServlet.invalidPath=FALLO - Se ha especificado una trayectoria inválida de contexto {0} +managerServlet.invalidWar=FALLO - Se ha especificado una URL de aplicación inválida {0} +managerServlet.listed=OK - Aplicaciones listadas para máquinda virutal {0} +managerServlet.listitem={0}:{1}:{2}:{3} +managerServlet.noAppBase=FALLO - No puedo identificar aplicación base para trayectoria de contexto {0} +managerServlet.noCommand=FALLO - No se ha especificado comando +managerServlet.noContext=FALLO - No existe contexto para trayectoria {0} +managerServlet.noDirectory=FALLO - Documento base No-directorio para trayectoria {0} +managerServlet.noDocBase=FALLO - No puedo replegar documento base para trayectoria {0} +managerServlet.noGlobal=FALLO - No hay disponibles recursos globales JNDI +managerServlet.noReload=FALLO - Recarga no soportada en WAR desplegado en trayectoria {0} +managerServlet.noRename=FALLO - No pudeo desplegar WAR cargado para trayectoria {0} +managerServlet.noRole=FALLO - El usuario no desempeña el papel de {0} +managerServlet.noSelf=FALLO - El gestor no puede recargarse, replegarse, pararse o replegarse a sí mismo +managerServlet.noWrapper=El Contenedor no ha llamado a setWrapper() para este servlet +managerServlet.reloaded=OK - Recargada aplicación en trayectoria de contexto {0} +managerServlet.undeployd=OK - Replegada aplicación en trayectoria de contexto {0} +managerServlet.resourcesAll=OK - Listados recursos globales de todos los tipos +managerServlet.resourcesType=OK - Listados recursos globales de tipo {0} +managerServlet.rolesList=OK - Listados papeles de seguridad +managerServlet.saveFail=FAIL - Fallo al guardar la configuración: {0} +managerServlet.saved=OK - Configuración de Servidor guardada +managerServlet.savedContext=OK - Configuración de Contexto {0} guardada +managerServlet.sessiondefaultmax=Intervalo máximo por defecto de sesión inactiva {0} minutos +managerServlet.sessiontimeout={0} minutos: {1} sesiones +managerServlet.sessions=OK - Información de sesión para aplicación en trayectoria de contexto {0} +managerServlet.started=OK - Arrancada aplicación en trayectoria de contexto {0} +managerServlet.startFailed=FALLO - No se pudo arrancar la aplicación en trayectoria de contexto {0} +managerServlet.stopped=OK - Parada aplicación en trayectoria de contexto {0} +managerServlet.undeployed=OK - Replegada aplicacación en trayectoria de contexto {0} +managerServlet.unknownCommand=FALLO - Comando desconocido {0} +managerServlet.userDatabaseError=FALLO - No puedo resolver referencia de base de datos de usuario +managerServlet.userDatabaseMissing=FALLO - No se encuentra disponible base de datos de usuario + +statusServlet.title=Estado de Servidor +statusServlet.complete=Estado Completo de Servidor diff --git a/java/org/apache/catalina/manager/LocalStrings_fr.properties b/java/org/apache/catalina/manager/LocalStrings_fr.properties new file mode 100644 index 000000000..4a2e1f99a --- /dev/null +++ b/java/org/apache/catalina/manager/LocalStrings_fr.properties @@ -0,0 +1,64 @@ +htmlManagerServlet.appsAvailable=Fonctionnant +htmlManagerServlet.appsName=Nom d''affichage +htmlManagerServlet.appsPath=Chemin +htmlManagerServlet.appsReload=Recharger +htmlManagerServlet.appsRemove=Retirer +htmlManagerServlet.appsSessions=Sessions +htmlManagerServlet.appsStart=Démarrer +htmlManagerServlet.appsStop=Arréter +htmlManagerServlet.appsTitle=Applications +htmlManagerServlet.installButton=Installation +htmlManagerServlet.installConfig=URL de configuration: +htmlManagerServlet.installPath=Chemin: +htmlManagerServlet.installTitle=Installation +htmlManagerServlet.installWar=URL du WAR: +htmlManagerServlet.messageLabel=Message: +htmlManagerServlet.serverJVMVendor=Fournisseur de la JVM +htmlManagerServlet.serverJVMVersion=Version de la JVM +htmlManagerServlet.serverOSArch=Architecture d''OS +htmlManagerServlet.serverOSName=Nom d''OS +htmlManagerServlet.serverOSVersion=Version d''OS +htmlManagerServlet.serverTitle=Serveur +htmlManagerServlet.serverVersion=Version de serveur +htmlManagerServlet.title=Gestionnaire d''applications WEB Tomcat +managerServlet.alreadyContext=ECHEC - l''application existe déjà dans le chemin {0} +managerServlet.alreadyDocBase=ECHEC - Le répertoire {0} est déjà utilisé +managerServlet.cannotInvoke=Impossible d''utiliser le gestionnaire de servlet au travers du délégué (invoker) +managerServlet.configured=OK - Application configurée depuis le fichier contexte {0} +managerServlet.deployed=OK - Application déployée pour le chemin de contexte {0} +managerServlet.exception=ECHEC - L''exception {0} a été rencontrée +managerServlet.installed=OK - Application installée pour le chemin de contexte {0} +managerServlet.invalidPath=ECHEC - Un chemin de contexte invalide {0} a été spécifié +managerServlet.invalidWar=ECHEC - Une URL d''application invalide {0} a été spécifiée +managerServlet.listed=OK - Applications listées pour l''hôte virtuel (virtual host) {0} +managerServlet.listitem={0}:{1}:{2}:{3} +managerServlet.noAppBase=ECHEC - Impossible d''identifier la base de l''application base pour le chemin de context {0} +managerServlet.noCommand=ECHEC - Aucune commande n''a été spécifiée +managerServlet.noContext=ECHEC - Aucune contexte n''existe pour le chemin {0} +managerServlet.noDirectory=ECHEC - La base de document n''est pas un répertoire pour le chemin {0} +managerServlet.noDocBase=ECHEC - Impossible de retirer la base de document pour le chemin {0} +managerServlet.noGlobal=ECHEC - Aucune ressource JNDI globale n''est disponible +managerServlet.noReload=ECHEC - Rechargement non supporté par le WAR déployé au chemin {0} +managerServlet.noRename=ECHEC - Impossible de déployer un WAR téléchargé pour le chemin {0} +managerServlet.noRole=ECHEC - L''utilisateur ne possède pas le rôle {0} +managerServlet.noSelf=ECHEC - Le gestionnaire ne peut recharger, retirer, arrêter, ou se déployer lui-même +managerServlet.noWrapper=Le conteneur n''a pas appelé "setWrapper()" pour cette servlet +managerServlet.reloaded=OK - Application rechargée au chemin de contexte {0} +managerServlet.removed=OK - Application retirée au chemin de contexte {0} +managerServlet.resourcesAll=OK - Liste des ressources globales de tout type +managerServlet.resourcesType=OK - Liste des ressources globales de type {0} +managerServlet.rolesList=OK - Liste de rôles de securité +managerServlet.saveFail=ECHEC - La sauvegarde de la configuration a échoué: {0} +managerServlet.sessiondefaultmax=Interval par défaut de maximum de session inactive {0} minutes +managerServlet.sessiontimeout={0} minutes:{1} sessions +managerServlet.sessions=OK - Information de session pour l''application au chemin de contexte {0} +managerServlet.started=OK - Application démarrée pour le chemin de contexte {0} +managerServlet.startFailed=ECHEC - L''application pour le chemin de contexte {0} n''a pas puêtredémarrée +managerServlet.stopped=OK - Application arrétée pour le chemin de contexte {0} +managerServlet.undeployed=OK - Application non-déployée pour le chemin de contexte {0} +managerServlet.unknownCommand=ECHEC - Commande inconnue {0} +managerServlet.userDatabaseError=ECHEC - Impossible de résoudre la base de données utilisateurs deréférence +managerServlet.userDatabaseMissing=ECHEC - Aucune base de données utilisateurs n''est disponible + +statusServlet.title=Etat du serveur +statusServlet.complete=Etat complet du serveur diff --git a/java/org/apache/catalina/manager/LocalStrings_ja.properties b/java/org/apache/catalina/manager/LocalStrings_ja.properties new file mode 100644 index 000000000..ac307de94 --- /dev/null +++ b/java/org/apache/catalina/manager/LocalStrings_ja.properties @@ -0,0 +1,79 @@ +htmlManagerServlet.appsAvailable=\u5b9f\u884c\u4e2d +htmlManagerServlet.appsName=\u8868\u793a\u540d +htmlManagerServlet.appsPath=\u30d1\u30b9 +htmlManagerServlet.appsReload=\u518d\u30ed\u30fc\u30c9 +htmlManagerServlet.appsUndeploy=\u914d\u5099\u89e3\u9664 +htmlManagerServlet.appsSessions=\u30bb\u30c3\u30b7\u30e7\u30f3 +htmlManagerServlet.appsStart=\u8d77\u52d5 +htmlManagerServlet.appsStop=\u505c\u6b62 +htmlManagerServlet.appsTasks=\u30b3\u30de\u30f3\u30c9 +htmlManagerServlet.appsTitle=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 +htmlManagerServlet.helpHtmlManager=HTML\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7 +htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html +htmlManagerServlet.helpManager=\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7 +htmlManagerServlet.helpManagerFile=manager-howto.html +htmlManagerServlet.deployButton=\u914d\u5099 +htmlManagerServlet.deployConfig=XML\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eURL: +htmlManagerServlet.deployPath=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 (\u7701\u7565\u53ef): +htmlManagerServlet.deployServer=\u30b5\u30fc\u30d0\u4e0a\u306eWAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u914d\u5099 +htmlManagerServlet.deployTitle=\u914d\u5099 +htmlManagerServlet.deployUpload=WAR\u30d5\u30a1\u30a4\u30eb\u306e\u914d\u5099 +htmlManagerServlet.deployUploadFail=FAIL - \u914d\u5099\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u4f8b\u5916: {0} +htmlManagerServlet.deployUploadFile=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308bWAR\u30d5\u30a1\u30a4\u30eb\u306e\u9078\u629e +htmlManagerServlet.deployUploadNotWar=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093 +htmlManagerServlet.deployUploadNoFile=FAIL - \u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u307e\u305b\u3093 +htmlManagerServlet.deployUploadWarExists=FAIL - WAR\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306f\u65e2\u306b\u30b5\u30fc\u30d0\u4e0a\u306b\u5b58\u5728\u3057\u307e\u3059 +htmlManagerServlet.deployWar=WAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306eURL: +htmlManagerServlet.list=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u4e00\u89a7 +htmlManagerServlet.manager=\u30de\u30cd\u30fc\u30b8\u30e3 +htmlManagerServlet.messageLabel=\u30e1\u30c3\u30bb\u30fc\u30b8 +htmlManagerServlet.serverJVMVendor=JVM\u30d9\u30f3\u30c0 +htmlManagerServlet.serverJVMVersion=JVM\u30d0\u30fc\u30b8\u30e7\u30f3 +htmlManagerServlet.serverOSArch=OS\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3 +htmlManagerServlet.serverOSName=OS\u540d +htmlManagerServlet.serverOSVersion=OS\u30d0\u30fc\u30b8\u30e7\u30f3 +htmlManagerServlet.serverTitle=\u30b5\u30fc\u30d0\u60c5\u5831 +htmlManagerServlet.serverVersion=Tomcat\u30d0\u30fc\u30b8\u30e7\u30f3 +htmlManagerServlet.title=Tomcat Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30de\u30cd\u30fc\u30b8\u30e3 +managerServlet.alreadyContext=FAIL - \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3001\u65e2\u306b\u30d1\u30b9 {0} \u306b\u5b58\u5728\u3057\u307e\u3059 +managerServlet.alreadyDocBase=FAIL - \u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u65e2\u306b\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059 +managerServlet.cannotInvoke=\u30a4\u30f3\u30dc\u30fc\u30ab\u3067\u30de\u30cd\u30fc\u30b8\u30e3\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f +managerServlet.configured=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb {0} \u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3057\u305f +managerServlet.deployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3057\u305f +managerServlet.exception=FAIL - \u4f8b\u5916 {0} \u304c\u767a\u751f\u3057\u307e\u3057\u305f +managerServlet.deployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3057\u305f +managerServlet.invalidPath=FAIL - \u7121\u52b9\u306a\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f +managerServlet.invalidWar=FAIL - \u7121\u52b9\u306a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306eURL {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f +managerServlet.listed=OK - \u30d0\u30fc\u30c1\u30e3\u30eb\u30db\u30b9\u30c8 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u4e00\u89a7\u3067\u3059 +managerServlet.listitem={0}:{1}:{2}:{3} +managerServlet.noAppBase=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u5bfe\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d9\u30fc\u30b9\u3092\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093 +managerServlet.noCommand=FAIL - \u30b3\u30de\u30f3\u30c9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +managerServlet.noContext=FAIL - \u30d1\u30b9 {0} \u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u304c\u5b58\u5728\u3057\u307e\u305b\u3093 +managerServlet.noDirectory=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093 +managerServlet.noDocBase=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093 +managerServlet.noGlobal=FAIL - \u30b0\u30ed\u30fc\u30d0\u30eb\u306aJNDI\u30ea\u30bd\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093 +managerServlet.noReload=FAIL - \u30d1\u30b9 {0} \u306b\u914d\u5099\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u518d\u30ed\u30fc\u30c9\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093 +managerServlet.noRename=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30b9 {0} \u306b\u914d\u5099\u3067\u304d\u307e\u305b\u3093 +managerServlet.noRole=FAIL - \u30e6\u30fc\u30b6\u306f\u30ed\u30fc\u30eb {0} \u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093 +managerServlet.noSelf=FAIL - \u30de\u30cd\u30fc\u30b8\u30e3\u81ea\u8eab\u3092\u518d\u30ed\u30fc\u30c9\u3001\u524a\u9664\u3001\u505c\u6b62\u3001\u53c8\u306f\u914d\u5099\u89e3\u9664\u3067\u304d\u307e\u305b\u3093 +managerServlet.noWrapper=\u30b3\u30f3\u30c6\u30ca\u306f\u3053\u306e\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u547c\u3073\u51fa\u3055\u308c\u305fsetWrapper()\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093 +managerServlet.reloaded=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f +managerServlet.undeployd=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u89e3\u9664\u3057\u307e\u3057\u305f +managerServlet.resourcesAll=OK - \u3059\u3079\u3066\u306e\u30bf\u30a4\u30d7\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f +managerServlet.resourcesType=OK - \u30bf\u30a4\u30d7 {0} \u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f +managerServlet.rolesList=OK - \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u3092\u5217\u6319\u3057\u307e\u3057\u305f +managerServlet.saveFail=FAIL - \u8a2d\u5b9a\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0} +managerServlet.saved=OK - \u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f +managerServlet.savedContext=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f +managerServlet.sessiondefaultmax=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u6700\u5927\u30bb\u30c3\u30b7\u30e7\u30f3\u505c\u6b62\u9593\u9694\u306f{0}\u5206\u3067\u3059 +managerServlet.sessiontimeout={0}\u5206: {1}\u30bb\u30c3\u30b7\u30e7\u30f3 +managerServlet.sessions=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u60c5\u5831\u3067\u3059 +managerServlet.started=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u307e\u3057\u305f +managerServlet.startFailed=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093 +managerServlet.stopped=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u505c\u6b62\u3057\u307e\u3057\u305f +managerServlet.undeployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u89e3\u9664\u3057\u307e\u3057\u305f +managerServlet.unknownCommand=FAIL - \u672a\u77e5\u306e\u30b3\u30de\u30f3\u30c9 {0} \u3067\u3059 +managerServlet.userDatabaseError=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u53c2\u7167\u3092\u89e3\u6c7a\u3067\u304d\u307e\u305b\u3093 +managerServlet.userDatabaseMissing=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093 +statusServlet.title=\u30b5\u30fc\u30d0\u306e\u72b6\u614b +statusServlet.complete=\u30b5\u30fc\u30d0\u306e\u5168\u72b6\u614b diff --git a/java/org/apache/catalina/manager/ManagerServlet.java b/java/org/apache/catalina/manager/ManagerServlet.java new file mode 100644 index 000000000..d71ed0c2f --- /dev/null +++ b/java/org/apache/catalina/manager/ManagerServlet.java @@ -0,0 +1,1582 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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.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.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. + *

+ * This servlet examines the value returned by getPathInfo() + * and related query parameters to determine what action is being requested. + * The following actions and parameters (starting after the servlet path) + * are supported: + *

+ *

Use path=/ for the ROOT context.

+ *

The syntax of the URL for a web application archive must conform to one + * of the following patterns to be successfully deployed:

+ * + *

+ * NOTE - 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. + *

+ * NOTE - 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. + *

+ * The following servlet initialization parameters are recognized: + *

+ * + * @author Craig R. McClanahan + * @author Remy Maucherat + * @version $Revision: 393613 $ $Date: 2006-04-12 23:08:01 +0200 (mer., 12 avr. 2006) $ + */ + +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 NamingContext 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")) { + sessions(writer, path); + } 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 { + 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 + + + /** + * 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) { + 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()) { + writer.println(sm.getString("managerServlet.deployed", 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 null 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 org.apache.catalina.UserDatabase resource that is + * connected to the users 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 MaxInactiveInterval timeouts listing number + * of sessions for each 10 minute timeout 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) { + + if (debug >= 1) + log("sessions: Session information for 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; + } + writer.println(sm.getString("managerServlet.sessions", displayPath)); + writer.println(sm.getString("managerServlet.sessiondefaultmax", + "" + context.getManager().getMaxInactiveInterval()/60)); + Session [] sessions = context.getManager().findSessions(); + int [] timeout = new int[60]; + int notimeout = 0; + for (int i = 0; i < sessions.length; i++) { + int time = sessions[i].getMaxInactiveInterval()/(10*60); + if (time < 0) + notimeout++; + else if (time >= timeout.length) + timeout[timeout.length-1]++; + else + timeout[time]++; + } + if (timeout[0] > 0) + writer.println(sm.getString("managerServlet.sessiontimeout", + "<10", "" + timeout[0])); + for (int i = 1; i < timeout.length-1; i++) { + if (timeout[i] > 0) + writer.println(sm.getString("managerServlet.sessiontimeout", + "" + (i)*10 + " - <" + (i+1)*10, + "" + timeout[i])); + } + if (timeout[timeout.length-1] > 0) + writer.println(sm.getString("managerServlet.sessiontimeout", + ">=" + timeout.length*10, + "" + timeout[timeout.length-1])); + if (notimeout > 0) + writer.println(sm.getString("managerServlet.sessiontimeout", + "unlimited","" + notimeout)); + } catch (Throwable t) { + log("ManagerServlet.sessions[" + displayPath + "]", t); + writer.println(sm.getString("managerServlet.exception", + t.toString())); + } + + } + + + /** + * 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 (!isServiced(path)) { + addServiced(path); + try { + // Try to stop the context first to be nicer + ((Lifecycle) context).stop(); + } catch (Throwable t) { + // Ignore + } + try { + 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); + } finally { + removeServiced(path); + } + } + 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 config file name. + */ + protected String getDocBase(String path) { + String basename = null; + if (path.equals("")) { + basename = "ROOT"; + } else { + basename = path.substring(1); + } + 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 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 check 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 check 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 check 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; + + } + + +} diff --git a/java/org/apache/catalina/manager/StatusManagerServlet.java b/java/org/apache/catalina/manager/StatusManagerServlet.java new file mode 100644 index 000000000..abb9050fc --- /dev/null +++ b/java/org/apache/catalina/manager/StatusManagerServlet.java @@ -0,0 +1,358 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +import javax.management.MBeanServer; +import javax.management.MBeanServerNotification; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.util.ServerInfo; +import org.apache.catalina.util.StringManager; +import org.apache.tomcat.util.modeler.Registry; + +/** + * This servlet will display a complete status of the HTTP/1.1 connector. + * + * @author Remy Maucherat + * @version $Revision: 303870 $ $Date: 2005-04-19 00:50:24 +0200 (mar., 19 avr. 2005) $ + */ + +public class StatusManagerServlet + extends HttpServlet implements NotificationListener { + + + // ----------------------------------------------------- Instance Variables + + + /** + * The debugging detail level for this servlet. + */ + private int debug = 0; + + + /** + * MBean server. + */ + protected MBeanServer mBeanServer = null; + + + /** + * Vector of protocol handlers object names. + */ + protected Vector protocolHandlers = new Vector(); + + + /** + * Vector of thread pools object names. + */ + protected Vector threadPools = new Vector(); + + + /** + * Vector of request processors object names. + */ + protected Vector requestProcessors = new Vector(); + + + /** + * Vector of global request processors object names. + */ + protected Vector globalRequestProcessors = new Vector(); + + + /** + * The string manager for this package. + */ + protected static StringManager sm = + StringManager.getManager(Constants.Package); + + + // --------------------------------------------------------- Public Methods + + + /** + * Initialize this servlet. + */ + public void init() throws ServletException { + + // Retrieve the MBean server + mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); + + // Set our properties from the initialization parameters + String value = null; + try { + value = getServletConfig().getInitParameter("debug"); + debug = Integer.parseInt(value); + } catch (Throwable t) { + ; + } + + try { + + // Query protocol handlers + String onStr = "*:type=ProtocolHandler,*"; + ObjectName objectName = new ObjectName(onStr); + Set set = mBeanServer.queryMBeans(objectName, null); + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + ObjectInstance oi = (ObjectInstance) iterator.next(); + protocolHandlers.addElement(oi.getObjectName()); + } + + // Query Thread Pools + onStr = "*:type=ThreadPool,*"; + objectName = new ObjectName(onStr); + set = mBeanServer.queryMBeans(objectName, null); + iterator = set.iterator(); + while (iterator.hasNext()) { + ObjectInstance oi = (ObjectInstance) iterator.next(); + threadPools.addElement(oi.getObjectName()); + } + + // Query Global Request Processors + onStr = "*:type=GlobalRequestProcessor,*"; + objectName = new ObjectName(onStr); + set = mBeanServer.queryMBeans(objectName, null); + iterator = set.iterator(); + while (iterator.hasNext()) { + ObjectInstance oi = (ObjectInstance) iterator.next(); + globalRequestProcessors.addElement(oi.getObjectName()); + } + + // Query Request Processors + onStr = "*:type=RequestProcessor,*"; + objectName = new ObjectName(onStr); + set = mBeanServer.queryMBeans(objectName, null); + iterator = set.iterator(); + while (iterator.hasNext()) { + ObjectInstance oi = (ObjectInstance) iterator.next(); + requestProcessors.addElement(oi.getObjectName()); + } + + // Register with MBean server + onStr = "JMImplementation:type=MBeanServerDelegate"; + objectName = new ObjectName(onStr); + mBeanServer.addNotificationListener(objectName, this, null, null); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + + /** + * 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 { + + // mode is flag for HTML or XML output + int mode = 0; + // if ?XML=true, set the mode to XML + if (request.getParameter("XML") != null + && request.getParameter("XML").equals("true")) { + mode = 1; + } + StatusTransformer.setContentType(response, mode); + + PrintWriter writer = response.getWriter(); + + boolean completeStatus = false; + if ((request.getPathInfo() != null) + && (request.getPathInfo().equals("/all"))) { + completeStatus = true; + } + // use StatusTransformer to output status + StatusTransformer.writeHeader(writer,mode); + + // Body Header Section + Object[] args = new Object[2]; + args[0] = request.getContextPath(); + if (completeStatus) { + args[1] = sm.getString("statusServlet.complete"); + } else { + args[1] = sm.getString("statusServlet.title"); + } + // use StatusTransformer to output status + StatusTransformer.writeBody(writer,args,mode); + + // Manager Section + args = new Object[9]; + args[0] = sm.getString("htmlManagerServlet.manager"); + args[1] = response.encodeURL(request.getContextPath() + "/html/list"); + args[2] = sm.getString("htmlManagerServlet.list"); + args[3] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlManagerServlet.helpHtmlManagerFile")); + args[4] = sm.getString("htmlManagerServlet.helpHtmlManager"); + args[5] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlManagerServlet.helpManagerFile")); + args[6] = sm.getString("htmlManagerServlet.helpManager"); + if (completeStatus) { + args[7] = response.encodeURL + (request.getContextPath() + "/status"); + args[8] = sm.getString("statusServlet.title"); + } else { + args[7] = response.encodeURL + (request.getContextPath() + "/status/all"); + args[8] = sm.getString("statusServlet.complete"); + } + // use StatusTransformer to output status + StatusTransformer.writeManager(writer,args,mode); + + // Server Header Section + args = new Object[7]; + args[0] = sm.getString("htmlManagerServlet.serverTitle"); + args[1] = sm.getString("htmlManagerServlet.serverVersion"); + args[2] = sm.getString("htmlManagerServlet.serverJVMVersion"); + args[3] = sm.getString("htmlManagerServlet.serverJVMVendor"); + args[4] = sm.getString("htmlManagerServlet.serverOSName"); + args[5] = sm.getString("htmlManagerServlet.serverOSVersion"); + args[6] = sm.getString("htmlManagerServlet.serverOSArch"); + // use StatusTransformer to output status + StatusTransformer.writePageHeading(writer,args,mode); + + // Server Row Section + args = new Object[6]; + args[0] = ServerInfo.getServerInfo(); + args[1] = System.getProperty("java.runtime.version"); + args[2] = System.getProperty("java.vm.vendor"); + args[3] = System.getProperty("os.name"); + args[4] = System.getProperty("os.version"); + args[5] = System.getProperty("os.arch"); + // use StatusTransformer to output status + StatusTransformer.writeServerInfo(writer, args, mode); + + try { + + // Display operating system statistics using APR if available + StatusTransformer.writeOSState(writer,mode); + + // Display virtual machine statistics + StatusTransformer.writeVMState(writer,mode); + + Enumeration enumeration = threadPools.elements(); + while (enumeration.hasMoreElements()) { + ObjectName objectName = (ObjectName) enumeration.nextElement(); + String name = objectName.getKeyProperty("name"); + // use StatusTransformer to output status + StatusTransformer.writeConnectorState + (writer, objectName, + name, mBeanServer, globalRequestProcessors, + requestProcessors, mode); + } + + if ((request.getPathInfo() != null) + && (request.getPathInfo().equals("/all"))) { + // Note: Retrieving the full status is much slower + // use StatusTransformer to output status + StatusTransformer.writeDetailedState + (writer, mBeanServer, mode); + } + + } catch (Exception e) { + throw new ServletException(e); + } + + // use StatusTransformer to output status + StatusTransformer.writeFooter(writer, mode); + + } + + // ------------------------------------------- NotificationListener Methods + + + public void handleNotification(Notification notification, + java.lang.Object handback) { + + if (notification instanceof MBeanServerNotification) { + ObjectName objectName = + ((MBeanServerNotification) notification).getMBeanName(); + if (notification.getType().equals + (MBeanServerNotification.REGISTRATION_NOTIFICATION)) { + String type = objectName.getKeyProperty("type"); + if (type != null) { + if (type.equals("ProtocolHandler")) { + protocolHandlers.addElement(objectName); + } else if (type.equals("ThreadPool")) { + threadPools.addElement(objectName); + } else if (type.equals("GlobalRequestProcessor")) { + globalRequestProcessors.addElement(objectName); + } else if (type.equals("RequestProcessor")) { + requestProcessors.addElement(objectName); + } + } + } else if (notification.getType().equals + (MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) { + String type = objectName.getKeyProperty("type"); + if (type != null) { + if (type.equals("ProtocolHandler")) { + protocolHandlers.removeElement(objectName); + } else if (type.equals("ThreadPool")) { + threadPools.removeElement(objectName); + } else if (type.equals("GlobalRequestProcessor")) { + globalRequestProcessors.removeElement(objectName); + } else if (type.equals("RequestProcessor")) { + requestProcessors.removeElement(objectName); + } + } + String j2eeType = objectName.getKeyProperty("j2eeType"); + if (j2eeType != null) { + + } + } + } + + } + + +} diff --git a/java/org/apache/catalina/manager/StatusTransformer.java b/java/org/apache/catalina/manager/StatusTransformer.java new file mode 100644 index 000000000..2bdf5baca --- /dev/null +++ b/java/org/apache/catalina/manager/StatusTransformer.java @@ -0,0 +1,933 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.Date; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +import javax.management.MBeanServer; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.util.RequestUtil; + +/** + * This is a refactoring of the servlet to externalize + * the output into a simple class. Although we could + * use XSLT, that is unnecessarily complex. + * + * @author Peter Lin + * @version $Revision: 303967 $ $Date: 2005-06-29 19:31:56 +0200 (mer., 29 juin 2005) $ + */ + +public class StatusTransformer { + + + // --------------------------------------------------------- Public Methods + + + public static void setContentType(HttpServletResponse response, + int mode) { + if (mode == 0){ + response.setContentType("text/html;charset="+Constants.CHARSET); + } else if (mode == 1){ + response.setContentType("text/xml;charset="+Constants.CHARSET); + } + } + + + /** + * 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 static void writeHeader(PrintWriter writer, int mode) { + if (mode == 0){ + // HTML Header Section + writer.print(Constants.HTML_HEADER_SECTION); + } else if (mode == 1){ + writer.write(Constants.XML_DECLARATION); + writer.write + (Constants.XML_STYLE); + writer.write(""); + } + } + + + /** + * Write the header body. XML output doesn't bother + * to output this stuff, since it's just title. + * + * @param writer The output writer + * @param args What to write + * @param mode 0 means write + */ + public static void writeBody(PrintWriter writer, Object[] args, int mode) { + if (mode == 0){ + writer.print(MessageFormat.format + (Constants.BODY_HEADER_SECTION, args)); + } + } + + + /** + * Write the manager webapp information. + * + * @param writer The output writer + * @param args What to write + * @param mode 0 means write + */ + public static void writeManager(PrintWriter writer, Object[] args, + int mode) { + if (mode == 0){ + writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args)); + } + } + + + public static void writePageHeading(PrintWriter writer, Object[] args, + int mode) { + if (mode == 0){ + writer.print(MessageFormat.format + (Constants.SERVER_HEADER_SECTION, args)); + } + } + + + public static void writeServerInfo(PrintWriter writer, Object[] args, + int mode){ + if (mode == 0){ + writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args)); + } + } + + + /** + * + */ + public static void writeFooter(PrintWriter writer, int mode) { + if (mode == 0){ + // HTML Tail Section + writer.print(Constants.HTML_TAIL_SECTION); + } else if (mode == 1){ + writer.write(""); + } + } + + + /** + * Write the OS state. Mode 0 will generate HTML. + * Mode 1 will generate XML. + */ + public static void writeOSState(PrintWriter writer, int mode) { + long[] result = new long[16]; + boolean ok = false; + try { + String methodName = "info"; + Class paramTypes[] = new Class[1]; + paramTypes[0] = result.getClass(); + Object paramValues[] = new Object[1]; + paramValues[0] = result; + Method method = Class.forName("org.apache.tomcat.jni.OS") + .getMethod(methodName, paramTypes); + method.invoke(null, paramValues); + ok = true; + } catch (Throwable t) { + // Ignore + } + + if (ok) { + if (mode == 0){ + writer.print("

OS

"); + + writer.print("

"); + writer.print(" Physical memory: "); + writer.print(formatSize(new Long(result[0]), true)); + writer.print(" Available memory: "); + writer.print(formatSize(new Long(result[1]), true)); + writer.print(" Total page file: "); + writer.print(formatSize(new Long(result[2]), true)); + writer.print(" Free page file: "); + writer.print(formatSize(new Long(result[3]), true)); + writer.print(" Memory load: "); + writer.print(new Long(result[6])); + writer.print("
"); + writer.print(" Process kernel time: "); + writer.print(formatTime(new Long(result[11] / 1000), true)); + writer.print(" Process user time: "); + writer.print(formatTime(new Long(result[12] / 1000), true)); + writer.print("

"); + } else if (mode == 1){ + } + } + + } + + + /** + * Write the VM state. Mode 0 will generate HTML. + * Mode 1 will generate XML. + */ + public static void writeVMState(PrintWriter writer, int mode) + throws Exception { + + if (mode == 0){ + writer.print("

JVM

"); + + writer.print("

"); + writer.print(" Free memory: "); + writer.print(formatSize + (new Long(Runtime.getRuntime().freeMemory()), true)); + writer.print(" Total memory: "); + writer.print(formatSize + (new Long(Runtime.getRuntime().totalMemory()), true)); + writer.print(" Max memory: "); + writer.print(formatSize + (new Long(Runtime.getRuntime().maxMemory()), true)); + writer.print("

"); + } else if (mode == 1){ + writer.write(""); + + writer.write(""); + + writer.write(""); + } + + } + + + /** + * Write connector state. + */ + public static void writeConnectorState(PrintWriter writer, + ObjectName tpName, String name, + MBeanServer mBeanServer, + Vector globalRequestProcessors, + Vector requestProcessors, + int mode) + throws Exception { + + if (mode == 0) { + writer.print("

"); + writer.print(name); + writer.print("

"); + + writer.print("

"); + writer.print(" Max threads: "); + writer.print(mBeanServer.getAttribute(tpName, "maxThreads")); + writer.print(" Min spare threads: "); + writer.print(mBeanServer.getAttribute(tpName, "minSpareThreads")); + writer.print(" Max spare threads: "); + writer.print(mBeanServer.getAttribute(tpName, "maxSpareThreads")); + writer.print(" Current thread count: "); + writer.print(mBeanServer.getAttribute(tpName, "currentThreadCount")); + writer.print(" Current thread busy: "); + writer.print(mBeanServer.getAttribute(tpName, "currentThreadsBusy")); + try { + Object value = mBeanServer.getAttribute(tpName, "keepAliveCount"); + writer.print(" Keeped alive sockets count: "); + writer.print(value); + } catch (Exception e) { + // Ignore + } + + writer.print("
"); + + ObjectName grpName = null; + + Enumeration enumeration = globalRequestProcessors.elements(); + while (enumeration.hasMoreElements()) { + ObjectName objectName = (ObjectName) enumeration.nextElement(); + if (name.equals(objectName.getKeyProperty("name"))) { + grpName = objectName; + } + } + + if (grpName == null) { + return; + } + + writer.print(" Max processing time: "); + writer.print(formatTime(mBeanServer.getAttribute + (grpName, "maxTime"), false)); + writer.print(" Processing time: "); + writer.print(formatTime(mBeanServer.getAttribute + (grpName, "processingTime"), true)); + writer.print(" Request count: "); + writer.print(mBeanServer.getAttribute(grpName, "requestCount")); + writer.print(" Error count: "); + writer.print(mBeanServer.getAttribute(grpName, "errorCount")); + writer.print(" Bytes received: "); + writer.print(formatSize(mBeanServer.getAttribute + (grpName, "bytesReceived"), true)); + writer.print(" Bytes sent: "); + writer.print(formatSize(mBeanServer.getAttribute + (grpName, "bytesSent"), true)); + writer.print("

"); + + writer.print(""); + + enumeration = requestProcessors.elements(); + while (enumeration.hasMoreElements()) { + ObjectName objectName = (ObjectName) enumeration.nextElement(); + if (name.equals(objectName.getKeyProperty("worker"))) { + writer.print(""); + writeProcessorState(writer, objectName, mBeanServer, mode); + writer.print(""); + } + } + + writer.print("
StageTimeB SentB RecvClientVHostRequest
"); + + writer.print("

"); + writer.print("P: Parse and prepare request S: Service F: Finishing R: Ready K: Keepalive"); + writer.print("

"); + } else if (mode == 1){ + writer.write(""); + + writer.write(""); + + ObjectName grpName = null; + + Enumeration enumeration = globalRequestProcessors.elements(); + while (enumeration.hasMoreElements()) { + ObjectName objectName = (ObjectName) enumeration.nextElement(); + if (name.equals(objectName.getKeyProperty("name"))) { + grpName = objectName; + } + } + + if (grpName != null) { + + writer.write(""); + + writer.write(""); + enumeration = requestProcessors.elements(); + while (enumeration.hasMoreElements()) { + ObjectName objectName = (ObjectName) enumeration.nextElement(); + if (name.equals(objectName.getKeyProperty("worker"))) { + writeProcessorState(writer, objectName, mBeanServer, mode); + } + } + writer.write(""); + } + + writer.write(""); + } + + } + + + /** + * Write processor state. + */ + protected static void writeProcessorState(PrintWriter writer, + ObjectName pName, + MBeanServer mBeanServer, + int mode) + throws Exception { + + Integer stageValue = + (Integer) mBeanServer.getAttribute(pName, "stage"); + int stage = stageValue.intValue(); + boolean fullStatus = true; + boolean showRequest = true; + String stageStr = null; + + switch (stage) { + + case (1/*org.apache.coyote.Constants.STAGE_PARSE*/): + stageStr = "P"; + fullStatus = false; + break; + case (2/*org.apache.coyote.Constants.STAGE_PREPARE*/): + stageStr = "P"; + fullStatus = false; + break; + case (3/*org.apache.coyote.Constants.STAGE_SERVICE*/): + stageStr = "S"; + break; + case (4/*org.apache.coyote.Constants.STAGE_ENDINPUT*/): + stageStr = "F"; + break; + case (5/*org.apache.coyote.Constants.STAGE_ENDOUTPUT*/): + stageStr = "F"; + break; + case (7/*org.apache.coyote.Constants.STAGE_ENDED*/): + stageStr = "R"; + fullStatus = false; + break; + case (6/*org.apache.coyote.Constants.STAGE_KEEPALIVE*/): + stageStr = "K"; + fullStatus = true; + showRequest = false; + break; + case (0/*org.apache.coyote.Constants.STAGE_NEW*/): + stageStr = "R"; + fullStatus = false; + break; + default: + // Unknown stage + stageStr = "?"; + fullStatus = false; + + } + + if (mode == 0) { + writer.write(""); + writer.write(stageStr); + writer.write(""); + + if (fullStatus) { + writer.write(""); + writer.print(formatTime(mBeanServer.getAttribute + (pName, "requestProcessingTime"), false)); + writer.write(""); + writer.write(""); + if (showRequest) { + writer.print(formatSize(mBeanServer.getAttribute + (pName, "requestBytesSent"), false)); + } else { + writer.write("?"); + } + writer.write(""); + writer.write(""); + if (showRequest) { + writer.print(formatSize(mBeanServer.getAttribute + (pName, "requestBytesReceived"), + false)); + } else { + writer.write("?"); + } + writer.write(""); + writer.write(""); + writer.print(filter(mBeanServer.getAttribute + (pName, "remoteAddr"))); + writer.write(""); + writer.write(""); + writer.write(filter(mBeanServer.getAttribute + (pName, "virtualHost"))); + writer.write(""); + writer.write(""); + if (showRequest) { + writer.write(filter(mBeanServer.getAttribute + (pName, "method"))); + writer.write(" "); + writer.write(filter(mBeanServer.getAttribute + (pName, "currentUri"))); + String queryString = (String) mBeanServer.getAttribute + (pName, "currentQueryString"); + if ((queryString != null) && (!queryString.equals(""))) { + writer.write("?"); + writer.print(RequestUtil.filter(queryString)); + } + writer.write(" "); + writer.write(filter(mBeanServer.getAttribute + (pName, "protocol"))); + } else { + writer.write("?"); + } + writer.write(""); + } else { + writer.write("??????"); + } + } else if (mode == 1){ + writer.write(""); + } + + } + + + /** + * Write applications state. + */ + public static void writeDetailedState(PrintWriter writer, + MBeanServer mBeanServer, int mode) + throws Exception { + + if (mode == 0){ + ObjectName queryHosts = new ObjectName("*:j2eeType=WebModule,*"); + Set hostsON = mBeanServer.queryNames(queryHosts, null); + + // Navigation menu + writer.print("

"); + writer.print("Application list"); + writer.print("

"); + + writer.print("

"); + int count = 0; + Iterator iterator = hostsON.iterator(); + while (iterator.hasNext()) { + ObjectName contextON = (ObjectName) iterator.next(); + String webModuleName = contextON.getKeyProperty("name"); + if (webModuleName.startsWith("//")) { + webModuleName = webModuleName.substring(2); + } + int slash = webModuleName.indexOf("/"); + if (slash == -1) { + count++; + continue; + } + + writer.print(""); + writer.print(webModuleName); + writer.print(""); + if (iterator.hasNext()) { + writer.print("
"); + } + + } + writer.print("

"); + + // Webapp list + count = 0; + iterator = hostsON.iterator(); + while (iterator.hasNext()) { + ObjectName contextON = (ObjectName) iterator.next(); + writer.print(""); + writeContext(writer, contextON, mBeanServer, mode); + } + + } else if (mode == 1){ + // for now we don't write out the Detailed state in XML + } + + } + + + /** + * Write context state. + */ + protected static void writeContext(PrintWriter writer, + ObjectName objectName, + MBeanServer mBeanServer, int mode) + throws Exception { + + if (mode == 0){ + String webModuleName = objectName.getKeyProperty("name"); + String name = webModuleName; + if (name == null) { + return; + } + + String hostName = null; + String contextName = null; + if (name.startsWith("//")) { + name = name.substring(2); + } + int slash = name.indexOf("/"); + if (slash != -1) { + hostName = name.substring(0, slash); + contextName = name.substring(slash); + } else { + return; + } + + ObjectName queryManager = new ObjectName + (objectName.getDomain() + ":type=Manager,path=" + contextName + + ",host=" + hostName + ",*"); + Set managersON = mBeanServer.queryNames(queryManager, null); + ObjectName managerON = null; + Iterator iterator2 = managersON.iterator(); + while (iterator2.hasNext()) { + managerON = (ObjectName) iterator2.next(); + } + + ObjectName queryJspMonitor = new ObjectName + (objectName.getDomain() + ":type=JspMonitor,WebModule=" + + webModuleName + ",*"); + Set jspMonitorONs = mBeanServer.queryNames(queryJspMonitor, null); + + // Special case for the root context + if (contextName.equals("/")) { + contextName = ""; + } + + writer.print("

"); + writer.print(name); + writer.print("

"); + writer.print("
"); + + writer.print("

"); + Object startTime = mBeanServer.getAttribute(objectName, + "startTime"); + writer.print(" Start time: " + + new Date(((Long) startTime).longValue())); + writer.print(" Startup time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "startupTime"), false)); + writer.print(" TLD scan time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "tldScanTime"), false)); + if (managerON != null) { + writeManager(writer, managerON, mBeanServer, mode); + } + if (jspMonitorONs != null) { + writeJspMonitor(writer, jspMonitorONs, mBeanServer, mode); + } + writer.print("

"); + + String onStr = objectName.getDomain() + + ":j2eeType=Servlet,WebModule=" + webModuleName + ",*"; + ObjectName servletObjectName = new ObjectName(onStr); + Set set = mBeanServer.queryMBeans(servletObjectName, null); + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + ObjectInstance oi = (ObjectInstance) iterator.next(); + writeWrapper(writer, oi.getObjectName(), mBeanServer, mode); + } + + } else if (mode == 1){ + // for now we don't write out the context in XML + } + + } + + + /** + * Write detailed information about a manager. + */ + public static void writeManager(PrintWriter writer, ObjectName objectName, + MBeanServer mBeanServer, int mode) + throws Exception { + + if (mode == 0) { + writer.print("
"); + writer.print(" Active sessions: "); + writer.print(mBeanServer.getAttribute + (objectName, "activeSessions")); + writer.print(" Session count: "); + writer.print(mBeanServer.getAttribute + (objectName, "sessionCounter")); + writer.print(" Max active sessions: "); + writer.print(mBeanServer.getAttribute(objectName, "maxActive")); + writer.print(" Rejected session creations: "); + writer.print(mBeanServer.getAttribute + (objectName, "rejectedSessions")); + writer.print(" Expired sessions: "); + writer.print(mBeanServer.getAttribute + (objectName, "expiredSessions")); + writer.print(" Longest session alive time: "); + writer.print(formatSeconds(mBeanServer.getAttribute( + objectName, + "sessionMaxAliveTime"))); + writer.print(" Average session alive time: "); + writer.print(formatSeconds(mBeanServer.getAttribute( + objectName, + "sessionAverageAliveTime"))); + writer.print(" Processing time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "processingTime"), false)); + } else if (mode == 1) { + // for now we don't write out the wrapper details + } + + } + + + /** + * Write JSP monitoring information. + */ + public static void writeJspMonitor(PrintWriter writer, + Set jspMonitorONs, + MBeanServer mBeanServer, + int mode) + throws Exception { + + int jspCount = 0; + int jspReloadCount = 0; + + Iterator iter = jspMonitorONs.iterator(); + while (iter.hasNext()) { + ObjectName jspMonitorON = (ObjectName) iter.next(); + Object obj = mBeanServer.getAttribute(jspMonitorON, "jspCount"); + jspCount += ((Integer) obj).intValue(); + obj = mBeanServer.getAttribute(jspMonitorON, "jspReloadCount"); + jspReloadCount += ((Integer) obj).intValue(); + } + + if (mode == 0) { + writer.print("
"); + writer.print(" JSPs loaded: "); + writer.print(jspCount); + writer.print(" JSPs reloaded: "); + writer.print(jspReloadCount); + } else if (mode == 1) { + // for now we don't write out anything + } + } + + + /** + * Write detailed information about a wrapper. + */ + public static void writeWrapper(PrintWriter writer, ObjectName objectName, + MBeanServer mBeanServer, int mode) + throws Exception { + + if (mode == 0) { + String servletName = objectName.getKeyProperty("name"); + + String[] mappings = (String[]) + mBeanServer.invoke(objectName, "findMappings", null, null); + + writer.print("

"); + writer.print(servletName); + if ((mappings != null) && (mappings.length > 0)) { + writer.print(" [ "); + for (int i = 0; i < mappings.length; i++) { + writer.print(mappings[i]); + if (i < mappings.length - 1) { + writer.print(" , "); + } + } + writer.print(" ] "); + } + writer.print("

"); + + writer.print("

"); + writer.print(" Processing time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "processingTime"), true)); + writer.print(" Max time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "maxTime"), false)); + writer.print(" Request count: "); + writer.print(mBeanServer.getAttribute(objectName, "requestCount")); + writer.print(" Error count: "); + writer.print(mBeanServer.getAttribute(objectName, "errorCount")); + writer.print(" Load time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "loadTime"), false)); + writer.print(" Classloading time: "); + writer.print(formatTime(mBeanServer.getAttribute + (objectName, "classLoadTime"), false)); + writer.print("

"); + } else if (mode == 1){ + // for now we don't write out the wrapper details + } + + } + + + /** + * Filter the specified message string for characters that are sensitive + * in HTML. This avoids potential attacks caused by including JavaScript + * codes in the request URL that is often reported in error messages. + * + * @param obj The message string to be filtered + */ + public static String filter(Object obj) { + + if (obj == null) + return ("?"); + String message = obj.toString(); + + char content[] = new char[message.length()]; + message.getChars(0, message.length(), content, 0); + StringBuffer result = new StringBuffer(content.length + 50); + for (int i = 0; i < content.length; i++) { + switch (content[i]) { + case '<': + result.append("<"); + break; + case '>': + result.append(">"); + break; + case '&': + result.append("&"); + break; + case '"': + result.append("""); + break; + default: + result.append(content[i]); + } + } + return (result.toString()); + + } + + + /** + * Display the given size in bytes, either as KB or MB. + * + * @param mb true to display megabytes, false for kilobytes + */ + public static String formatSize(Object obj, boolean mb) { + + long bytes = -1L; + + if (obj instanceof Long) { + bytes = ((Long) obj).longValue(); + } else if (obj instanceof Integer) { + bytes = ((Integer) obj).intValue(); + } + + if (mb) { + long mbytes = bytes / (1024 * 1024); + long rest = + ((bytes - (mbytes * (1024 * 1024))) * 100) / (1024 * 1024); + return (mbytes + "." + ((rest < 10) ? "0" : "") + rest + " MB"); + } else { + return ((bytes / 1024) + " KB"); + } + + } + + + /** + * Display the given time in ms, either as ms or s. + * + * @param seconds true to display seconds, false for milliseconds + */ + public static String formatTime(Object obj, boolean seconds) { + + long time = -1L; + + if (obj instanceof Long) { + time = ((Long) obj).longValue(); + } else if (obj instanceof Integer) { + time = ((Integer) obj).intValue(); + } + + if (seconds) { + return ((((float) time ) / 1000) + " s"); + } else { + return (time + " ms"); + } + } + + + /** + * Formats the given time (given in seconds) as a string. + * + * @param obj Time object to be formatted as string + * + * @return String formatted time + */ + public static String formatSeconds(Object obj) { + + long time = -1L; + + if (obj instanceof Long) { + time = ((Long) obj).longValue(); + } else if (obj instanceof Integer) { + time = ((Integer) obj).intValue(); + } + + return (time + " s"); + } + +} diff --git a/java/org/apache/catalina/manager/host/Constants.java b/java/org/apache/catalina/manager/host/Constants.java new file mode 100644 index 000000000..84b3db67b --- /dev/null +++ b/java/org/apache/catalina/manager/host/Constants.java @@ -0,0 +1,203 @@ +/* + * Copyright 1999,2004 The Apache Software Foundation. + * + * Licensed 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.host; + + +public class Constants { + + public static final String Package = "org.apache.catalina.manager.host"; + + public static final String HTML_HEADER_SECTION = + "\n" + + "\n" + + "\n"; + + public static final String BODY_HEADER_SECTION = + "{0}\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \"The\n" + + " \n" + + " \n" + + " \"The\n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " {1}\n" + + "
\n" + + "
\n" + + "\n"; + + public static final String MESSAGE_SECTION = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
" + + "{0} 
{1}
\n" + + "
\n" + + "\n"; + + public static final String MANAGER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
{0}
{2}{4}{6}{8}
\n" + + "
\n" + + "\n"; + + public static final String SERVER_HEADER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + public static final String SERVER_ROW_SECTION = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "
{0}
{1}{2}{3}{4}{5}{6}
{0}{1}{2}{3}{4}{5}
\n" + + "
\n" + + "\n"; + + public static final String HTML_TAIL_SECTION = + "
\n" + + "
\n" + + " Copyright © 1999-2005, Apache Software Foundation" + + "
\n" + + "\n" + + "\n" + + ""; + public static final String CHARSET="utf-8"; + + // FIXME need we this? + public static final String XML_DECLARATION = + ""; + + public static final String XML_STYLE = + ""; + +} + diff --git a/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java b/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java new file mode 100644 index 000000000..c377e8054 --- /dev/null +++ b/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java @@ -0,0 +1,478 @@ +/* + * Copyright 1999,2004-2006 The Apache Software Foundation. + * + * Licensed 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.host; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.Container; +import org.apache.catalina.Host; +import org.apache.catalina.util.ServerInfo; + +/** +* Servlet that enables remote management of the virtual hosts deployed +* on the server. Normally, this functionality will be protected by a security +* constraint in the web application deployment descriptor. However, +* this requirement can be relaxed during testing. +*

+* The difference between the HostManagerServlet and this +* Servlet is that this Servlet prints out a HTML interface which +* makes it easier to administrate. +*

+* However if you use a software that parses the output of +* HostManagerServlet you won't be able to upgrade +* to this Servlet since the output are not in the +* same format as from HostManagerServlet +* +* @author Bip Thelin +* @author Malcolm Edgar +* @author Glenn L. Nielsen +* @author Peter Rossbach +* @version $Revision: 384293 $, $Date: 2006-03-08 19:09:36 +0100 (mer., 08 mars 2006) $ +* @see ManagerServlet +*/ + +public final class HTMLHostManagerServlet extends HostManagerServlet { + + // --------------------------------------------------------- Public Methods + + /** + * 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 { + + // Identify the request parameters that we need + String command = request.getPathInfo(); + + String name = request.getParameter("name"); + + // Prepare our output writer to generate the response message + response.setContentType("text/html; charset=" + Constants.CHARSET); + + String message = ""; + // Process the requested command + if (command == null) { + } else if (command.equals("/add")) { + message = add(request, name); + } else if (command.equals("/remove")) { + message = remove(name); + } else if (command.equals("/list")) { + } else if (command.equals("/start")) { + message = start(name); + } else if (command.equals("/stop")) { + message = stop(name); + } else { + message = + sm.getString("hostManagerServlet.unknownCommand", command); + } + + list(request, response, message); + } + + + /** + * Add a host using the specified parameters. + * + * @param name host name + */ + protected String add(HttpServletRequest request,String name) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.add(request,printWriter,name,true); + + return stringWriter.toString(); + } + + + /** + * Remove the specified host. + * + * @param writer Writer to render results to + * @param name host name + */ + protected String remove(String name) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.remove(printWriter, name); + + return stringWriter.toString(); + } + + + /** + * Start the host with the specified name. + * + * @param name Host name + */ + protected String start(String name) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.start(printWriter, name); + + return stringWriter.toString(); + } + + + /** + * Stop the host with the specified name. + * + * @param name Host name + */ + protected String stop(String name) { + + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + + super.stop(printWriter, name); + + return stringWriter.toString(); + } + + + /** + * Render a HTML list of the currently active Contexts in our virtual host, + * and memory and server status information. + * + * @param request The request + * @param response The response + * @param message a message to display + */ + public void list(HttpServletRequest request, + HttpServletResponse response, + String message) throws IOException { + + PrintWriter writer = response.getWriter(); + + // HTML Header Section + writer.print(Constants.HTML_HEADER_SECTION); + + // Body Header Section + Object[] args = new Object[2]; + args[0] = request.getContextPath(); + args[1] = sm.getString("htmlHostManagerServlet.title"); + writer.print(MessageFormat.format + (Constants.BODY_HEADER_SECTION, args)); + + // Message Section + args = new Object[3]; + args[0] = sm.getString("htmlHostManagerServlet.messageLabel"); + args[1] = (message == null || message.length() == 0) ? "OK" : message; + writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args)); + + // Manager Section + args = new Object[9]; + args[0] = sm.getString("htmlHostManagerServlet.manager"); + args[1] = response.encodeURL(request.getContextPath() + "/html/list"); + args[2] = sm.getString("htmlHostManagerServlet.list"); + args[3] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlHostManagerServlet.helpHtmlManagerFile")); + args[4] = sm.getString("htmlHostManagerServlet.helpHtmlManager"); + args[5] = response.encodeURL + (request.getContextPath() + "/" + + sm.getString("htmlHostManagerServlet.helpManagerFile")); + args[6] = sm.getString("htmlHostManagerServlet.helpManager"); + args[7] = response.encodeURL("/manager/status"); + args[8] = sm.getString("statusServlet.title"); + writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args)); + + // Hosts Header Section + args = new Object[3]; + args[0] = sm.getString("htmlHostManagerServlet.hostName"); + args[1] = sm.getString("htmlHostManagerServlet.hostAliases"); + args[2] = sm.getString("htmlHostManagerServlet.hostTasks"); + writer.print(MessageFormat.format(HOSTS_HEADER_SECTION, args)); + + // Hosts Row Section + // Create sorted map of host names. + Container[] children = engine.findChildren(); + String hostNames[] = new String[children.length]; + for (int i = 0; i < children.length; i++) + hostNames[i] = children[i].getName(); + + TreeMap sortedHostNamesMap = new TreeMap(); + + for (int i = 0; i < hostNames.length; i++) { + String displayPath = hostNames[i]; + sortedHostNamesMap.put(displayPath, hostNames[i]); + } + + String hostsStart = sm.getString("htmlHostManagerServlet.hostsStart"); + String hostsStop = sm.getString("htmlHostManagerServlet.hostsStop"); + String hostsRemove = sm.getString("htmlHostManagerServlet.hostsRemove"); + + Iterator iterator = sortedHostNamesMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + String hostName = (String) entry.getKey(); + Host host = (Host) engine.findChild(hostName); + + if (host != null ) { + args = new Object[2]; + args[0] = hostName; + String[] aliases = host.findAliases(); + StringBuffer buf = new StringBuffer(); + if (aliases.length > 0) { + buf.append(aliases[0]); + for (int j = 1; j < aliases.length; j++) { + buf.append(", ").append(aliases[j]); + } + } + + if (buf.length() == 0) { + buf.append(" "); + } + + args[1] = buf.toString(); + writer.print + (MessageFormat.format(HOSTS_ROW_DETAILS_SECTION, args)); + + args = new Object[7]; + args[0] = response.encodeURL + (request.getContextPath() + + "/html/start?name=" + hostName); + args[1] = hostsStart; + args[2] = response.encodeURL + (request.getContextPath() + + "/html/stop?name=" + hostName); + args[3] = hostsStop; + args[4] = response.encodeURL + (request.getContextPath() + + "/html/remove?name=" + hostName); + args[5] = hostsRemove; + args[6] = hostName; + if (host == this.host) { + writer.print(MessageFormat.format( + MANAGER_HOST_ROW_BUTTON_SECTION, args)); + } else { + writer.print(MessageFormat.format( + HOSTS_ROW_BUTTON_SECTION, args)); + } + + } + } + + // Add Section + args = new Object[6]; + args[0] = sm.getString("htmlHostManagerServlet.addTitle"); + args[1] = sm.getString("htmlHostManagerServlet.addHost"); + args[2] = response.encodeURL(request.getContextPath() + "/html/add"); + args[3] = sm.getString("htmlHostManagerServlet.addName"); + args[4] = sm.getString("htmlHostManagerServlet.addAliases"); + args[5] = sm.getString("htmlHostManagerServlet.addAppBase"); + writer.print(MessageFormat.format(ADD_SECTION_START, args)); + + args = new Object[3]; + args[0] = sm.getString("htmlHostManagerServlet.addAutoDeploy"); + args[1] = "autoDeploy"; + args[2] = "checked"; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + args[0] = sm.getString("htmlHostManagerServlet.addDeployOnStartup"); + args[1] = "deployOnStartup"; + args[2] = "checked"; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + args[0] = sm.getString("htmlHostManagerServlet.addDeployXML"); + args[1] = "deployXML"; + args[2] = "checked"; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + args[0] = sm.getString("htmlHostManagerServlet.addUnpackWARs"); + args[1] = "unpackWARs"; + args[2] = "checked"; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + args[0] = sm.getString("htmlHostManagerServlet.addXmlNamespaceAware"); + args[1] = "xmlNamespaceAware"; + args[2] = ""; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + args[0] = sm.getString("htmlHostManagerServlet.addXmlValidation"); + args[1] = "xmlValidation"; + args[2] = ""; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + + args[0] = sm.getString("htmlHostManagerServlet.addManager"); + args[1] = "manager"; + args[2] = "checked"; + writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args)); + + args = new Object[1]; + args[0] = sm.getString("htmlHostManagerServlet.addButton"); + writer.print(MessageFormat.format(ADD_SECTION_END, args)); + + // Server Header Section + args = new Object[7]; + args[0] = sm.getString("htmlHostManagerServlet.serverTitle"); + args[1] = sm.getString("htmlHostManagerServlet.serverVersion"); + args[2] = sm.getString("htmlHostManagerServlet.serverJVMVersion"); + args[3] = sm.getString("htmlHostManagerServlet.serverJVMVendor"); + args[4] = sm.getString("htmlHostManagerServlet.serverOSName"); + args[5] = sm.getString("htmlHostManagerServlet.serverOSVersion"); + args[6] = sm.getString("htmlHostManagerServlet.serverOSArch"); + writer.print(MessageFormat.format + (Constants.SERVER_HEADER_SECTION, args)); + + // Server Row Section + args = new Object[6]; + args[0] = ServerInfo.getServerInfo(); + args[1] = System.getProperty("java.runtime.version"); + args[2] = System.getProperty("java.vm.vendor"); + args[3] = System.getProperty("os.name"); + args[4] = System.getProperty("os.version"); + args[5] = System.getProperty("os.arch"); + writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args)); + + // HTML Tail Section + writer.print(Constants.HTML_TAIL_SECTION); + + // Finish up the response + writer.flush(); + writer.close(); + } + + + // ------------------------------------------------------ Private Constants + + // These HTML sections are broken in relatively small sections, because of + // limited number of subsitutions MessageFormat can process + // (maximium of 10). + + private static final String HOSTS_HEADER_SECTION = + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private static final String HOSTS_ROW_DETAILS_SECTION = + "\n" + + " \n" + + " \n"; + + private static final String MANAGER_HOST_ROW_BUTTON_SECTION = + " \n" + + "\n"; + + private static final String HOSTS_ROW_BUTTON_SECTION = + " \n" + + "\n"; + + private static final String ADD_SECTION_START = + "
{0}
{0}{1}{2}
{0}" + + "{1}\n" + + " \n" + + "  {1} \n" + + "  {3} \n" + + "  {5} \n" + + " \n" + + "
\n" + + " \n" + + "  {1} \n" + + "  {3} \n" + + "  {5} \n" + + " \n" + + "
\n" + + "
\n" + + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + "\n" + + "\n" + + " \n" + + "\n" + + "
{0}
{1}
\n" + + "
\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" ; + + private static final String ADD_SECTION_BOOLEAN = + "\n" + + " \n" + + " \n" + + "\n" ; + + private static final String ADD_SECTION_END = + "\n" + + " \n" + + " \n" + + "\n" + + "
\n" + + " {3}\n" + + " \n" + + " \n" + + "
\n" + + " {4}\n" + + " \n" + + " \n" + + "
\n" + + " {5}\n" + + " \n" + + " \n" + + "
\n" + + " {0}\n" + + " \n" + + " \n" + + "
\n" + + "  \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "\n"; + +} diff --git a/java/org/apache/catalina/manager/host/HostManagerServlet.java b/java/org/apache/catalina/manager/host/HostManagerServlet.java new file mode 100644 index 000000000..869814730 --- /dev/null +++ b/java/org/apache/catalina/manager/host/HostManagerServlet.java @@ -0,0 +1,684 @@ +/* + * Copyright 1999,2004-2006 The Apache Software Foundation. + * + * Licensed 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.host; + + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.StringTokenizer; + +import javax.management.MBeanServer; +import javax.servlet.ServletException; +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.Wrapper; +import org.apache.catalina.core.StandardHost; +import org.apache.catalina.startup.HostConfig; +import org.apache.catalina.util.StringManager; +import org.apache.tomcat.util.modeler.Registry; + + +/** + * Servlet that enables remote management of the virtual hosts installed + * on the server. Normally, this functionality will be protected by + * a security constraint in the web application deployment descriptor. + * However, this requirement can be relaxed during testing. + *

+ * This servlet examines the value returned by getPathInfo() + * and related query parameters to determine what action is being requested. + * The following actions and parameters (starting after the servlet path) + * are supported: + *

+ *

+ * NOTE - Attempting to stop or remove the host containing + * this servlet itself will not succeed. Therefore, this servlet should + * generally be deployed in a separate virtual host. + *

+ * NOTE - 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. + *

+ * The following servlet initialization parameters are recognized: + *

+ * + * @author Craig R. McClanahan + * @author Remy Maucherat + * @version $Revision: 384293 $ $Date: 2006-03-08 19:09:36 +0100 (mer., 08 mars 2006) $ + */ + +public class HostManagerServlet + 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; + + + /** + * The associated host. + */ + protected Host host = null; + + + /** + * The associated engine. + */ + protected Engine engine = null; + + + /** + * MBean server. + */ + protected MBeanServer mBeanServer = 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; + engine = null; + } else { + context = (Context) wrapper.getParent(); + host = (Host) context.getParent(); + engine = (Engine) host.getParent(); + } + + // 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("hostManagerServlet.cannotInvoke")); + + // Identify the request parameters that we need + String command = request.getPathInfo(); + if (command == null) + command = request.getServletPath(); + String name = request.getParameter("name"); + + // 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("hostManagerServlet.noCommand")); + } else if (command.equals("/add")) { + add(request, writer, name, false); + } else if (command.equals("/remove")) { + remove(writer, name); + } else if (command.equals("/list")) { + list(writer); + } else if (command.equals("/start")) { + start(writer, name); + } else if (command.equals("/stop")) { + stop(writer, name); + } else { + writer.println(sm.getString("hostManagerServlet.unknownCommand", + command)); + } + + // Finish up the response + writer.flush(); + writer.close(); + + } + + + /** + * Add host with the given parameters. + * + * @param request The request + * @param writer The output writer + * @param name The host name + * @param htmlMode Flag value + */ + protected void add(HttpServletRequest request, PrintWriter writer, String name, boolean htmlMode ) { + String aliases = request.getParameter("aliases"); + String appBase = request.getParameter("appBase"); + boolean manager = booleanParameter(request, "manager", false, htmlMode); + boolean autoDeploy = booleanParameter(request, "autoDeploy", true, htmlMode); + boolean deployOnStartup = booleanParameter(request, "deployOnStartup", true, htmlMode); + boolean deployXML = booleanParameter(request, "deployXML", true, htmlMode); + boolean unpackWARs = booleanParameter(request, "unpackWARs", true, htmlMode); + boolean xmlNamespaceAware = booleanParameter(request, "xmlNamespaceAware", false, htmlMode); + boolean xmlValidation = booleanParameter(request, "xmlValidation", false, htmlMode); + add(writer, name, aliases, appBase, manager, + autoDeploy, + deployOnStartup, + deployXML, + unpackWARs, + xmlNamespaceAware, + xmlValidation); + } + + + /** + * extract boolean value from checkbox with default + * @param request + * @param parameter + * @param theDefault + * @param htmlMode + * @return + */ + protected boolean booleanParameter(HttpServletRequest request, + String parameter, boolean theDefault, boolean htmlMode) { + String value = request.getParameter(parameter); + boolean booleanValue = theDefault; + if (value != null) { + if (htmlMode) { + if (value.equals("on")) { + booleanValue = true; + } + } else if (theDefault) { + if (value.equals("false")) { + booleanValue = false; + } + } else if (value.equals("true")) { + booleanValue = true; + } + } else if (htmlMode) + booleanValue = false; + return booleanValue; + } + + + /** + * 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("hostManagerServlet.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("hostManagerServlet.cannotInvoke")); + + // Set our properties from the initialization parameters + String value = null; + try { + value = getServletConfig().getInitParameter("debug"); + debug = Integer.parseInt(value); + } catch (Throwable t) { + ; + } + + } + + + + // -------------------------------------------------------- Private Methods + + + /** + * Add a host using the specified parameters. + * + * @param writer Writer to render results to + * @param name host name + * @param aliases comma separated alias list + * @param appBase application base for the host + * @param manager should the manager webapp be deployed to the new host ? + */ + protected synchronized void add + (PrintWriter writer, String name, String aliases, String appBase, + boolean manager, + boolean autoDeploy, + boolean deployOnStartup, + boolean deployXML, + boolean unpackWARs, + boolean xmlNamespaceAware, + boolean xmlValidation) { + if (debug >= 1) { + log("add: Adding host '" + name + "'"); + } + + // Validate the requested host name + if ((name == null) || name.length() == 0) { + writer.println(sm.getString("hostManagerServlet.invalidHostName", name)); + return; + } + + // Check if host already exists + if (engine.findChild(name) != null) { + writer.println + (sm.getString("hostManagerServlet.alreadyHost", name)); + return; + } + + // Validate and create appBase + File appBaseFile = null; + if (appBase == null || appBase.length() == 0) { + appBase = name; + } + File file = new File(appBase); + if (!file.isAbsolute()) + file = new File(System.getProperty("catalina.base"), appBase); + try { + appBaseFile = file.getCanonicalFile(); + } catch (IOException e) { + appBaseFile = file; + } + if (!appBaseFile.exists()) { + appBaseFile.mkdirs(); + } + + // Create base for config files + File configBaseFile = getConfigBase(name); + + // Copy manager.xml if requested + if (manager) { + InputStream is = null; + OutputStream os = null; + try { + is = getServletContext().getResourceAsStream("/manager.xml"); + os = new FileOutputStream(new File(configBaseFile, "manager.xml")); + byte buffer[] = new byte[512]; + int len = buffer.length; + while (true) { + len = is.read(buffer); + if (len == -1) + break; + os.write(buffer, 0, len); + } + } catch (IOException e) { + writer.println + (sm.getString("hostManagerServlet.managerXml")); + return; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + if (os != null) { + try { + os.close(); + } catch (IOException e) { + } + } + } + } + + StandardHost host = new StandardHost(); + host.setAppBase(appBase); + host.setName(name); + + host.addLifecycleListener(new HostConfig()); + + // Add host aliases + if ((aliases != null) && !("".equals(aliases))) { + StringTokenizer tok = new StringTokenizer(aliases, ","); + while (tok.hasMoreTokens()) { + host.addAlias(tok.nextToken()); + } + } + host.setAutoDeploy(autoDeploy); + host.setDeployOnStartup(deployOnStartup); + host.setDeployXML(deployXML); + host.setUnpackWARs(unpackWARs); + host.setXmlNamespaceAware(xmlNamespaceAware); + host.setXmlValidation(xmlValidation); + + // Add new host + try { + engine.addChild(host); + } catch (Exception e) { + writer.println(sm.getString("hostManagerServlet.exception", + e.toString())); + return; + } + + host = (StandardHost) engine.findChild(name); + if (host != null) { + writer.println(sm.getString("hostManagerServlet.add", name)); + } else { + // Something failed + writer.println(sm.getString("hostManagerServlet.addFailed", name)); + } + + } + + + /** + * Remove the specified host. + * + * @param writer Writer to render results to + * @param name host name + */ + protected synchronized void remove(PrintWriter writer, String name) { + + if (debug >= 1) { + log("remove: Removing host '" + name + "'"); + } + + // Validate the requested host name + if ((name == null) || name.length() == 0) { + writer.println(sm.getString("hostManagerServlet.invalidHostName", name)); + return; + } + + // Check if host exists + if (engine.findChild(name) == null) { + writer.println + (sm.getString("hostManagerServlet.noHost", name)); + return; + } + + // Prevent removing our own host + if (engine.findChild(name) == host) { + writer.println + (sm.getString("hostManagerServlet.cannotRemoveOwnHost", name)); + return; + } + + // Remove host + // Note that the host will not get physically removed + try { + engine.removeChild(engine.findChild(name)); + } catch (Exception e) { + writer.println(sm.getString("hostManagerServlet.exception", + e.toString())); + return; + } + + Host host = (StandardHost) engine.findChild(name); + if (host == null) { + writer.println(sm.getString("hostManagerServlet.remove", name)); + } else { + // Something failed + writer.println(sm.getString("hostManagerServlet.removeFailed", name)); + } + + } + + + /** + * 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 hosts for engine '" + + engine.getName() + "'"); + + writer.println(sm.getString("hostManagerServlet.listed", + engine.getName())); + Container[] hosts = engine.findChildren(); + for (int i = 0; i < hosts.length; i++) { + Host host = (Host) hosts[i]; + String name = host.getName(); + String[] aliases = host.findAliases(); + StringBuffer buf = new StringBuffer(); + if (aliases.length > 0) { + buf.append(aliases[0]); + for (int j = 1; j < aliases.length; j++) { + buf.append(',').append(aliases[j]); + } + } + writer.println(sm.getString("hostManagerServlet.listitem", + name, buf.toString())); + } + } + + + /** + * Start the host with the specified name. + * + * @param writer Writer to render to + * @param name Host name + */ + protected void start(PrintWriter writer, String name) { + + if (debug >= 1) + log("start: Starting host with name '" + name + "'"); + + // Validate the requested host name + if ((name == null) || name.length() == 0) { + writer.println(sm.getString("hostManagerServlet.invalidHostName", name)); + return; + } + + // Check if host exists + if (engine.findChild(name) == null) { + writer.println + (sm.getString("hostManagerServlet.noHost", name)); + return; + } + + // Prevent starting our own host + if (engine.findChild(name) == host) { + writer.println + (sm.getString("hostManagerServlet.cannotStartOwnHost", name)); + return; + } + + // Start host + try { + ((Lifecycle) engine.findChild(name)).start(); + writer.println + (sm.getString("hostManagerServlet.started", name)); + } catch (Throwable t) { + getServletContext().log + (sm.getString("hostManagerServlet.startFailed", name), t); + writer.println + (sm.getString("hostManagerServlet.startFailed", name)); + writer.println(sm.getString("hostManagerServlet.exception", + t.toString())); + return; + } + + } + + + /** + * Start the host with the specified name. + * + * @param writer Writer to render to + * @param name Host name + */ + protected void stop(PrintWriter writer, String name) { + + if (debug >= 1) + log("stop: Stopping host with name '" + name + "'"); + + // Validate the requested host name + if ((name == null) || name.length() == 0) { + writer.println(sm.getString("hostManagerServlet.invalidHostName", name)); + return; + } + + // Check if host exists + if (engine.findChild(name) == null) { + writer.println + (sm.getString("hostManagerServlet.noHost", name)); + return; + } + + // Prevent starting our own host + if (engine.findChild(name) == host) { + writer.println + (sm.getString("hostManagerServlet.cannotStopOwnHost", name)); + return; + } + + // Start host + try { + ((Lifecycle) engine.findChild(name)).stop(); + writer.println + (sm.getString("hostManagerServlet.stopped", name)); + } catch (Throwable t) { + getServletContext().log + (sm.getString("hostManagerServlet.stopFailed", name), t); + writer.println + (sm.getString("hostManagerServlet.stopFailed", name)); + writer.println(sm.getString("hostManagerServlet.exception", + t.toString())); + return; + } + + } + + + // -------------------------------------------------------- Support Methods + + + /** + * Get config base. + */ + protected File getConfigBase(String hostName) { + File configBase = + new File(System.getProperty("catalina.base"), "conf"); + if (!configBase.exists()) { + return null; + } + if (engine != null) { + configBase = new File(configBase, engine.getName()); + } + if (host != null) { + configBase = new File(configBase, hostName); + } + configBase.mkdirs(); + return configBase; + } + + +} diff --git a/java/org/apache/catalina/manager/host/LocalStrings.properties b/java/org/apache/catalina/manager/host/LocalStrings.properties new file mode 100644 index 000000000..5a665c984 --- /dev/null +++ b/java/org/apache/catalina/manager/host/LocalStrings.properties @@ -0,0 +1,59 @@ +hostManagerServlet.cannotInvoke=Cannot invoke host manager servlet through invoker +hostManagerServlet.noCommand=FAIL - No command was specified +hostManagerServlet.unknownCommand=FAIL - Unknown command {0} +hostManagerServlet.noWrapper=Container has not called setWrapper() for this servlet +hostManagerServlet.invalidHostName=FAIL - Invalid host name {0} was specified +hostManagerServlet.alreadyHost=FAIL - Host already exists with host name {0} +hostManagerServlet.managerXml=FAIL - Couldn't install manager.xml +hostManagerServlet.exception=FAIL - Encountered exception {0} +hostManagerServlet.add=OK - Host {0} added +hostManagerServlet.addFailed=FAIL - Failed to add host {0} +hostManagerServlet.cannotRemoveOwnHost=FAIL - Cannot remove own host {0} +hostManagerServlet.remove=OK - Removed host {0} +hostManagerServlet.removeFailed=FAIL - Failed to remove host {0} +hostManagerServlet.listed=OK - Listed hosts +hostManagerServlet.listitem={0}:{1} +hostManagerServlet.cannotStartOwnHost=FAIL - Cannot start own host {0} +hostManagerServlet.started=OK - Host {0} started +hostManagerServlet.startFailed=FAIL - Failed to start host {0} +hostManagerServlet.cannotStopOwnHost=FAIL - Cannot stop own host {0} +hostManagerServlet.stopped=OK - Host {0} stopped +hostManagerServlet.stopFailed=FAIL - Failed to stop host {0} + +htmlHostManagerServlet.title=Tomcat Virtual Host Manager +htmlHostManagerServlet.messageLabel=Message: +htmlHostManagerServlet.manager=Host Manager +htmlHostManagerServlet.list=List Virtual Hosts +htmlHostManagerServlet.helpHtmlManagerFile=html-host-manager-howto.html +htmlHostManagerServlet.helpHtmlManager=HTML Host Manager Help (Coming Soon!) +htmlHostManagerServlet.helpManagerFile=host-manager-howto.html +htmlHostManagerServlet.helpManager=Host Manager Help +htmlHostManagerServlet.hostName=Host name +htmlHostManagerServlet.hostAliases=Host aliases +htmlHostManagerServlet.hostTasks=Commands +htmlHostManagerServlet.hostsStart=Start +htmlHostManagerServlet.hostsStop=Stop +htmlHostManagerServlet.hostsRemove=Remove +htmlHostManagerServlet.addTitle=Add Virtual Host +htmlHostManagerServlet.addHost=Host +htmlHostManagerServlet.addName=Name: +htmlHostManagerServlet.addAliases=Aliases: +htmlHostManagerServlet.addAppBase=App base: +htmlHostManagerServlet.addManager=Manager App +htmlHostManagerServlet.addAutoDeploy=AutoDeploy +htmlHostManagerServlet.addDeployOnStartup=DeployOnStartup +htmlHostManagerServlet.addDeployXML=DeployXML +htmlHostManagerServlet.addUnpackWARs=UnpackWARs +htmlHostManagerServlet.addXmlNamespaceAware=XmlNamespaceAware +htmlHostManagerServlet.addXmlValidation=XmlValidation +htmlHostManagerServlet.addButton=Add +htmlHostManagerServlet.serverTitle=Server Information +htmlHostManagerServlet.serverVersion=Tomcat Version +htmlHostManagerServlet.serverJVMVersion=JVM Version +htmlHostManagerServlet.serverJVMVendor=JVM Vendor +htmlHostManagerServlet.serverOSName=OS Name +htmlHostManagerServlet.serverOSVersion=OS Version +htmlHostManagerServlet.serverOSArch=OS Architecture + +statusServlet.title=Server Status +statusServlet.complete=Complete Server Status diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java new file mode 100644 index 000000000..805b6d0a1 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java @@ -0,0 +1,607 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + + +/** + *

The default implementation of the + * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface. + * + *

After retrieving an instance of this class from a {@link + * org.apache.tomcat.util.http.fileupload.DiskFileUpload DiskFileUpload} instance (see + * {@link org.apache.tomcat.util.http.fileupload.DiskFileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of file at once using {@link #get()} or + * request an {@link java.io.InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $ + */ +public class DefaultFileItem + implements FileItem +{ + + // ----------------------------------------------------------- Data members + + + /** + * Counter used in unique identifier generation. + */ + private static int counter = 0; + + + /** + * The name of the form field as provided by the browser. + */ + private String fieldName; + + + /** + * The content type passed by the browser, or null if + * not defined. + */ + private String contentType; + + + /** + * Whether or not this item is a simple form field. + */ + private boolean isFormField; + + + /** + * The original filename in the user's filesystem. + */ + private String fileName; + + + /** + * The threshold above which uploads will be stored on disk. + */ + private int sizeThreshold; + + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private File repository; + + + /** + * Cached contents of the file. + */ + private byte[] cachedContent; + + + /** + * Output stream for this item. + */ + private DeferredFileOutputStream dfos; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs a new DefaultFileItem instance. + * + * @param fieldName The name of the form field. + * @param contentType The content type passed by the browser or + * null if not specified. + * @param isFormField Whether or not this item is a plain form field, as + * opposed to a file upload. + * @param fileName The original filename in the user's filesystem, or + * null if not specified. + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + DefaultFileItem(String fieldName, String contentType, boolean isFormField, + String fileName, int sizeThreshold, File repository) + { + this.fieldName = fieldName; + this.contentType = contentType; + this.isFormField = isFormField; + this.fileName = fileName; + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + + // ------------------------------- Methods from javax.activation.DataSource + + + /** + * Returns an {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @exception IOException if an error occurs. + */ + public InputStream getInputStream() + throws IOException + { + if (!dfos.isInMemory()) + { + return new FileInputStream(dfos.getFile()); + } + + if (cachedContent == null) + { + cachedContent = dfos.getData(); + } + return new ByteArrayInputStream(cachedContent); + } + + + /** + * Returns the content type passed by the browser or null if + * not defined. + * + * @return The content type passed by the browser or null if + * not defined. + */ + public String getContentType() + { + return contentType; + } + + + /** + * Returns the original filename in the client's filesystem. + * + * @return The original filename in the client's filesystem. + */ + public String getName() + { + return fileName; + } + + + // ------------------------------------------------------- FileItem methods + + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return true if the file contents will be read + * from memory; false otherwise. + */ + public boolean isInMemory() + { + return (dfos.isInMemory()); + } + + + /** + * Returns the size of the file. + * + * @return The size of the file, in bytes. + */ + public long getSize() + { + if (cachedContent != null) + { + return cachedContent.length; + } + else if (dfos.isInMemory()) + { + return dfos.getData().length; + } + else + { + return dfos.getFile().length(); + } + } + + + /** + * Returns the contents of the file as an array of bytes. If the + * contents of the file were not yet cached in memory, they will be + * loaded from the disk storage and cached. + * + * @return The contents of the file as an array of bytes. + */ + public byte[] get() + { + if (dfos.isInMemory()) + { + if (cachedContent == null) + { + cachedContent = dfos.getData(); + } + return cachedContent; + } + + byte[] fileData = new byte[(int) getSize()]; + FileInputStream fis = null; + + try + { + fis = new FileInputStream(dfos.getFile()); + fis.read(fileData); + } + catch (IOException e) + { + fileData = null; + } + finally + { + if (fis != null) + { + try + { + fis.close(); + } + catch (IOException e) + { + // ignore + } + } + } + + return fileData; + } + + + /** + * Returns the contents of the file as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * @param encoding The character encoding to use. + * + * @return The contents of the file, as a string. + * + * @exception UnsupportedEncodingException if the requested character + * encoding is not available. + */ + public String getString(String encoding) + throws UnsupportedEncodingException + { + return new String(get(), encoding); + } + + + /** + * Returns the contents of the file as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the file. + * + * @return The contents of the file, as a string. + */ + public String getString() + { + return new String(get()); + } + + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This implementation first attempts to rename the uploaded item to the + * specified destination file, if the item was originally written to disk. + * Otherwise, the data will be copied to the specified file. + *

+ * This method is only guaranteed to work once, the first time it + * is invoked for a particular item. This is because, in the event that the + * method renames a temporary file, that file will no longer be available + * to copy or rename again at a later time. + * + * @param file The File into which the uploaded item should + * be stored. + * + * @exception Exception if an error occurs. + */ + public void write(File file) throws Exception + { + if (isInMemory()) + { + FileOutputStream fout = null; + try + { + fout = new FileOutputStream(file); + fout.write(get()); + } + finally + { + if (fout != null) + { + fout.close(); + } + } + } + else + { + File outputFile = getStoreLocation(); + if (outputFile != null) + { + /* + * The uploaded file is being stored on disk + * in a temporary location so move it to the + * desired file. + */ + if (!outputFile.renameTo(file)) + { + BufferedInputStream in = null; + BufferedOutputStream out = null; + try + { + in = new BufferedInputStream( + new FileInputStream(outputFile)); + out = new BufferedOutputStream( + new FileOutputStream(file)); + byte[] bytes = new byte[2048]; + int s = 0; + while ((s = in.read(bytes)) != -1) + { + out.write(bytes, 0, s); + } + } + finally + { + try + { + in.close(); + } + catch (IOException e) + { + // ignore + } + try + { + out.close(); + } + catch (IOException e) + { + // ignore + } + } + } + } + else + { + /* + * For whatever reason we cannot write the + * file to disk. + */ + throw new FileUploadException( + "Cannot write uploaded file to disk!"); + } + } + } + + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. Although this storage will be deleted + * automatically when the FileItem instance is garbage + * collected, this method can be used to ensure that this is done at an + * earlier time, thus preserving system resources. + */ + public void delete() + { + cachedContent = null; + File outputFile = getStoreLocation(); + if (outputFile != null && outputFile.exists()) + { + outputFile.delete(); + } + } + + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + * + * @see #setFieldName(java.lang.String) + * + */ + public String getFieldName() + { + return fieldName; + } + + + /** + * Sets the field name used to reference this file item. + * + * @param fieldName The name of the form field. + * + * @see #getFieldName() + * + */ + public void setFieldName(String fieldName) + { + this.fieldName = fieldName; + } + + + /** + * Determines whether or not a FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + * + * @see #setFormField(boolean) + * + */ + public boolean isFormField() + { + return isFormField; + } + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false if it represents an uploaded file. + * + * @see #isFormField() + * + */ + public void setFormField(boolean state) + { + isFormField = state; + } + + + /** + * Returns an {@link java.io.OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link java.io.OutputStream OutputStream} that can be used + * for storing the contensts of the file. + * + * @exception IOException if an error occurs. + */ + public OutputStream getOutputStream() + throws IOException + { + if (dfos == null) + { + File outputFile = getTempFile(); + dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); + } + return dfos; + } + + + // --------------------------------------------------------- Public methods + + + /** + * Returns the {@link java.io.File} object for the FileItem's + * data's temporary location on the disk. Note that for + * FileItems that have their data stored in memory, + * this method will return null. When handling large + * files, you can use {@link java.io.File#renameTo(java.io.File)} to + * move the file to new location without copying the data, if the + * source and destination locations reside within the same logical + * volume. + * + * @return The data file, or null if the data is stored in + * memory. + */ + public File getStoreLocation() + { + return dfos.getFile(); + } + + + // ------------------------------------------------------ Protected methods + + + /** + * Removes the file contents from the temporary storage. + */ + protected void finalize() + { + File outputFile = dfos.getFile(); + + if (outputFile != null && outputFile.exists()) + { + outputFile.delete(); + } + } + + + /** + * Creates and returns a {@link java.io.File File} representing a uniquely + * named temporary file in the configured repository path. + * + * @return The {@link java.io.File File} to be used for temporary storage. + */ + protected File getTempFile() + { + File tempDir = repository; + if (tempDir == null) + { + tempDir = new File(System.getProperty("java.io.tmpdir")); + } + + String fileName = "upload_" + getUniqueId() + ".tmp"; + + File f = new File(tempDir, fileName); + f.deleteOnExit(); + return f; + } + + + // -------------------------------------------------------- Private methods + + + /** + * Returns an identifier that is unique within the class loader used to + * load this class, but does not have random-like apearance. + * + * @return A String with the non-random looking instance identifier. + */ + private static String getUniqueId() + { + int current; + synchronized (DefaultFileItem.class) + { + current = counter++; + } + String id = Integer.toString(current); + + // If you manage to get more than 100 million of ids, you'll + // start getting ids longer than 8 characters. + if (current < 100000000) + { + id = ("00000000" + id).substring(id.length()); + } + return id; + } + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java new file mode 100644 index 000000000..b710a0a67 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java @@ -0,0 +1,189 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + +import java.io.File; + + +/** + *

The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory} + * implementation. This implementation creates + * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep their + * content either in memory, for smaller items, or in a temporary file on disk, + * for larger items. The size threshold, above which content will be stored on + * disk, is configurable, as is the directory in which temporary files will be + * created.

+ * + *

If not otherwise configured, the default configuration values are as + * follows: + *

+ *

+ * + * @author Martin Cooper + * + * @version $Id: DefaultFileItemFactory.java,v 1.2 2003/05/31 22:31:08 martinc Exp $ + */ +public class DefaultFileItemFactory implements FileItemFactory +{ + + // ----------------------------------------------------- Manifest constants + + + /** + * The default threshold above which uploads will be stored on disk. + */ + public static final int DEFAULT_SIZE_THRESHOLD = 10240; + + + // ----------------------------------------------------- Instance Variables + + + /** + * The directory in which uploaded files will be stored, if stored on disk. + */ + private File repository; + + + /** + * The threshold above which uploads will be stored on disk. + */ + private int sizeThreshold = DEFAULT_SIZE_THRESHOLD; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an unconfigured instance of this class. The resulting factory + * may be configured by calling the appropriate setter methods. + */ + public DefaultFileItemFactory() + { + } + + + /** + * Constructs a preconfigured instance of this class. + * + * @param sizeThreshold The threshold, in bytes, below which items will be + * retained in memory and above which they will be + * stored as a file. + * @param repository The data repository, which is the directory in + * which files will be created, should the item size + * exceed the threshold. + */ + public DefaultFileItemFactory(int sizeThreshold, File repository) + { + this.sizeThreshold = sizeThreshold; + this.repository = repository; + } + + + // ------------------------------------------------------------- Properties + + + /** + * Returns the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @return The directory in which temporary files will be located. + * + * @see #setRepository(java.io.File) + * + */ + public File getRepository() + { + return repository; + } + + + /** + * Sets the directory used to temporarily store files that are larger + * than the configured size threshold. + * + * @param repository The directory in which temporary files will be located. + * + * @see #getRepository() + * + */ + public void setRepository(File repository) + { + this.repository = repository; + } + + + /** + * Returns the size threshold beyond which files are written directly to + * disk. The default value is 1024 bytes. + * + * @return The size threshold, in bytes. + * + * @see #setSizeThreshold(int) + */ + public int getSizeThreshold() + { + return sizeThreshold; + } + + + /** + * Sets the size threshold beyond which files are written directly to disk. + * + * @param sizeThreshold The size threshold, in bytes. + * + * @see #getSizeThreshold() + * + */ + public void setSizeThreshold(int sizeThreshold) + { + this.sizeThreshold = sizeThreshold; + } + + + // --------------------------------------------------------- Public Methods + + /** + * Create a new {@link org.apache.tomcat.util.http.fileupload.DefaultFileItem} + * instance from the supplied parameters and the local factory + * configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField true if this is a plain form field; + * false otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * + * @return The newly created file item. + */ + public FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ) + { + return new DefaultFileItem(fieldName, contentType, + isFormField, fileName, sizeThreshold, repository); + } + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java new file mode 100644 index 000000000..79c1fe758 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java @@ -0,0 +1,173 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + *

An output stream which will retain data in memory until a specified + * threshold is reached, and only then commit it to disk. If the stream is + * closed before the threshold is reached, the data will not be written to + * disk at all.

+ * + * @author Martin Cooper + * + * @version $Id: DeferredFileOutputStream.java,v 1.2 2003/05/31 22:31:08 martinc Exp $ + */ +public class DeferredFileOutputStream + extends ThresholdingOutputStream +{ + + // ----------------------------------------------------------- Data members + + + /** + * The output stream to which data will be written prior to the theshold + * being reached. + */ + private ByteArrayOutputStream memoryOutputStream; + + + /** + * The output stream to which data will be written after the theshold is + * reached. + */ + private FileOutputStream diskOutputStream; + + + /** + * The output stream to which data will be written at any given time. This + * will always be one of memoryOutputStream or + * diskOutputStream. + */ + private OutputStream currentOutputStream; + + + /** + * The file to which output will be directed if the threshold is exceeded. + */ + private File outputFile; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an instance of this class which will trigger an event at the + * specified threshold, and save data to a file beyond that point. + * + * @param threshold The number of bytes at which to trigger an event. + * @param outputFile The file to which data is saved beyond the threshold. + */ + public DeferredFileOutputStream(int threshold, File outputFile) + { + super(threshold); + this.outputFile = outputFile; + + memoryOutputStream = new ByteArrayOutputStream(threshold); + currentOutputStream = memoryOutputStream; + } + + + // --------------------------------------- ThresholdingOutputStream methods + + + /** + * Returns the current output stream. This may be memory based or disk + * based, depending on the current state with respect to the threshold. + * + * @return The underlying output stream. + * + * @exception IOException if an error occurs. + */ + protected OutputStream getStream() throws IOException + { + return currentOutputStream; + } + + + /** + * Switches the underlying output stream from a memory based stream to one + * that is backed by disk. This is the point at which we realise that too + * much data is being written to keep in memory, so we elect to switch to + * disk-based storage. + * + * @exception IOException if an error occurs. + */ + protected void thresholdReached() throws IOException + { + byte[] data = memoryOutputStream.toByteArray(); + FileOutputStream fos = new FileOutputStream(outputFile); + fos.write(data); + diskOutputStream = fos; + currentOutputStream = fos; + memoryOutputStream = null; + } + + + // --------------------------------------------------------- Public methods + + + /** + * Determines whether or not the data for this output stream has been + * retained in memory. + * + * @return true if the data is available in memory; + * false otherwise. + */ + public boolean isInMemory() + { + return (!isThresholdExceeded()); + } + + + /** + * Returns the data for this output stream as an array of bytes, assuming + * that the data has been retained in memory. If the data was written to + * disk, this method returns null. + * + * @return The data for this output stream, or null if no such + * data is available. + */ + public byte[] getData() + { + if (memoryOutputStream != null) + { + return memoryOutputStream.toByteArray(); + } + return null; + } + + + /** + * Returns the data for this output stream as a File, assuming + * that the data was written to disk. If the data was retained in memory, + * this method returns null. + * + * @return The file for this output stream, or null if no such + * file exists. + */ + public File getFile() + { + return outputFile; + } +} diff --git a/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java new file mode 100644 index 000000000..5b1bac9d8 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java @@ -0,0 +1,203 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +import java.io.File; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

Individual parts will be stored in temporary disk storage or in memory, + * depending on their size, and will be available as {@link + * org.apache.tomcat.util.http.fileupload.FileItem}s.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: DiskFileUpload.java,v 1.3 2003/06/01 00:18:13 martinc Exp $ + */ +public class DiskFileUpload + extends FileUploadBase + { + + // ----------------------------------------------------------- Data members + + + /** + * The factory to use to create new form items. + */ + private DefaultFileItemFactory fileItemFactory; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an instance of this class which uses the default factory to + * create FileItem instances. + * + * @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory) + */ + public DiskFileUpload() + { + super(); + this.fileItemFactory = new DefaultFileItemFactory(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see #DiskFileUpload() + */ + public DiskFileUpload(DefaultFileItemFactory fileItemFactory) + { + super(); + this.fileItemFactory = fileItemFactory; + } + + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public FileItemFactory getFileItemFactory() + { + return fileItemFactory; + } + + + /** + * Sets the factory class to use when creating file items. The factory must + * be an instance of DefaultFileItemFactory or a subclass + * thereof, or else a ClassCastException will be thrown. + * + * @param factory The factory class for new file items. + */ + public void setFileItemFactory(FileItemFactory factory) + { + this.fileItemFactory = (DefaultFileItemFactory) factory; + } + + + /** + * Returns the size threshold beyond which files are written directly to + * disk. + * + * @return The size threshold, in bytes. + * + * @see #setSizeThreshold(int) + */ + public int getSizeThreshold() + { + return fileItemFactory.getSizeThreshold(); + } + + + /** + * Sets the size threshold beyond which files are written directly to disk. + * + * @param sizeThreshold The size threshold, in bytes. + * + * @see #getSizeThreshold() + */ + public void setSizeThreshold(int sizeThreshold) + { + fileItemFactory.setSizeThreshold(sizeThreshold); + } + + + /** + * Returns the location used to temporarily store files that are larger + * than the configured size threshold. + * + * @return The path to the temporary file location. + * + * @see #setRepositoryPath(String) + */ + public String getRepositoryPath() + { + return fileItemFactory.getRepository().getPath(); + } + + + /** + * Sets the location used to temporarily store files that are larger + * than the configured size threshold. + * + * @param repositoryPath The path to the temporary file location. + * + * @see #getRepositoryPath() + */ + public void setRepositoryPath(String repositoryPath) + { + fileItemFactory.setRepository(new File(repositoryPath)); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. If files are stored + * on disk, the path is given by getRepository(). + * + * @param req The servlet request to be parsed. Must be non-null. + * @param sizeThreshold The max size in bytes to be stored in memory. + * @param sizeMax The maximum allowed upload size, in bytes. + * @param path The location where the files should be stored. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @exception FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List /* FileItem */ parseRequest(HttpServletRequest req, + int sizeThreshold, + long sizeMax, String path) + throws FileUploadException + { + setSizeThreshold(sizeThreshold); + setSizeMax(sizeMax); + setRepositoryPath(path); + return parseRequest(req); + } + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItem.java b/java/org/apache/tomcat/util/http/fileupload/FileItem.java new file mode 100644 index 000000000..5edecda93 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/FileItem.java @@ -0,0 +1,230 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; + + +/** + *

This class represents a file or form item that was received within a + * multipart/form-data POST request. + * + *

After retrieving an instance of this class from a {@link + * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see + * {@link org.apache.tomcat.util.http.fileupload.FileUpload + * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may + * either request all contents of the file at once using {@link #get()} or + * request an {@link java.io.InputStream InputStream} with + * {@link #getInputStream()} and process the file without attempting to load + * it into memory, which may come handy with large files. + * + *

While this interface does not extend + * javax.activation.DataSource per se (to avoid a seldom used + * dependency), several of the defined methods are specifically defined with + * the same signatures as methods in that interface. This allows an + * implementation of this interface to also implement + * javax.activation.DataSource with minimal additional work. + * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author Martin Cooper + * + * @version $Id: FileItem.java,v 1.15 2003/06/01 17:33:24 martinc Exp $ + */ +public interface FileItem + extends Serializable +{ + + + // ------------------------------- Methods from javax.activation.DataSource + + + /** + * Returns an {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @return An {@link java.io.InputStream InputStream} that can be + * used to retrieve the contents of the file. + * + * @exception IOException if an error occurs. + */ + InputStream getInputStream() + throws IOException; + + + /** + * Returns the content type passed by the browser or null if + * not defined. + * + * @return The content type passed by the browser or null if + * not defined. + */ + String getContentType(); + + + /** + * Returns the original filename in the client's filesystem, as provided by + * the browser (or other client software). In most cases, this will be the + * base file name, without path information. However, some clients, such as + * the Opera browser, do include path information. + * + * @return The original filename in the client's filesystem. + */ + String getName(); + + + // ------------------------------------------------------- FileItem methods + + + /** + * Provides a hint as to whether or not the file contents will be read + * from memory. + * + * @return true if the file contents will be read from memory; + * false otherwise. + */ + boolean isInMemory(); + + + /** + * Returns the size of the file item. + * + * @return The size of the file item, in bytes. + */ + long getSize(); + + + /** + * Returns the contents of the file item as an array of bytes. + * + * @return The contents of the file item as an array of bytes. + */ + byte[] get(); + + + /** + * Returns the contents of the file item as a String, using the specified + * encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @param encoding The character encoding to use. + * + * @return The contents of the item, as a string. + * + * @exception UnsupportedEncodingException if the requested character + * encoding is not available. + */ + String getString(String encoding) + throws UnsupportedEncodingException; + + + /** + * Returns the contents of the file item as a String, using the default + * character encoding. This method uses {@link #get()} to retrieve the + * contents of the item. + * + * @return The contents of the item, as a string. + */ + String getString(); + + + /** + * A convenience method to write an uploaded item to disk. The client code + * is not concerned with whether or not the item is stored in memory, or on + * disk in a temporary location. They just want to write the uploaded item + * to a file. + *

+ * This method is not guaranteed to succeed if called more than once for + * the same item. This allows a particular implementation to use, for + * example, file renaming, where possible, rather than copying all of the + * underlying data, thus gaining a significant performance benefit. + * + * @param file The File into which the uploaded item should + * be stored. + * + * @exception Exception if an error occurs. + */ + void write(File file) throws Exception; + + + /** + * Deletes the underlying storage for a file item, including deleting any + * associated temporary disk file. Although this storage will be deleted + * automatically when the FileItem instance is garbage + * collected, this method can be used to ensure that this is done at an + * earlier time, thus preserving system resources. + */ + void delete(); + + + /** + * Returns the name of the field in the multipart form corresponding to + * this file item. + * + * @return The name of the form field. + */ + String getFieldName(); + + + /** + * Sets the field name used to reference this file item. + * + * @param name The name of the form field. + */ + void setFieldName(String name); + + + /** + * Determines whether or not a FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + boolean isFormField(); + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + void setFormField(boolean state); + + + /** + * Returns an {@link java.io.OutputStream OutputStream} that can + * be used for storing the contents of the file. + * + * @return An {@link java.io.OutputStream OutputStream} that can be used + * for storing the contensts of the file. + * + * @exception IOException if an error occurs. + */ + OutputStream getOutputStream() throws IOException; + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java new file mode 100644 index 000000000..b85dbe8bf --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +/** + *

A factory interface for creating {@link FileItem} instances. Factories + * can provide their own custom configuration, over and above that provided + * by the default file upload implementation.

+ * + * @author Martin Cooper + * + * @version $Id: FileItemFactory.java,v 1.1 2003/04/27 17:30:06 martinc Exp $ + */ +public interface FileItemFactory +{ + + /** + * Create a new {@link FileItem} instance from the supplied parameters and + * any local factory configuration. + * + * @param fieldName The name of the form field. + * @param contentType The content type of the form field. + * @param isFormField true if this is a plain form field; + * false otherwise. + * @param fileName The name of the uploaded file, if any, as supplied + * by the browser or other client. + * + * @return The newly created file item. + */ + FileItem createItem( + String fieldName, + String contentType, + boolean isFormField, + String fileName + ); +} diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java new file mode 100644 index 000000000..af4dfd8b0 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java @@ -0,0 +1,110 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: FileUpload.java,v 1.23 2003/06/24 05:45:43 martinc Exp $ + */ +public class FileUpload + extends FileUploadBase + { + + // ----------------------------------------------------------- Data members + + + /** + * The factory to use to create new form items. + */ + private FileItemFactory fileItemFactory; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an instance of this class which uses the default factory to + * create FileItem instances. + * + * @see #FileUpload(FileItemFactory) + */ + public FileUpload() + { + super(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem instances. + * + * @see #FileUpload() + */ + public FileUpload(FileItemFactory fileItemFactory) + { + super(); + this.fileItemFactory = fileItemFactory; + } + + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public FileItemFactory getFileItemFactory() + { + return fileItemFactory; + } + + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + public void setFileItemFactory(FileItemFactory factory) + { + this.fileItemFactory = factory; + } + + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java new file mode 100644 index 000000000..1bfa3780d --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java @@ -0,0 +1,640 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

How the data for individual parts is stored is determined by the factory + * used to create them; a given part may be in memory, on disk, or somewhere + * else.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $ + */ +public abstract class FileUploadBase +{ + + // ---------------------------------------------------------- Class methods + + + /** + * Utility method that determines whether the request contains multipart + * content. + * + * @param req The servlet request to be evaluated. Must be non-null. + * + * @return true if the request is multipart; + * false otherwise. + */ + public static final boolean isMultipartContent(HttpServletRequest req) + { + String contentType = req.getHeader(CONTENT_TYPE); + if (contentType == null) + { + return false; + } + if (contentType.startsWith(MULTIPART)) + { + return true; + } + return false; + } + + + // ----------------------------------------------------- Manifest constants + + + /** + * HTTP content type header name. + */ + public static final String CONTENT_TYPE = "Content-type"; + + + /** + * HTTP content disposition header name. + */ + public static final String CONTENT_DISPOSITION = "Content-disposition"; + + + /** + * Content-disposition value for form data. + */ + public static final String FORM_DATA = "form-data"; + + + /** + * Content-disposition value for file attachment. + */ + public static final String ATTACHMENT = "attachment"; + + + /** + * Part of HTTP content type header. + */ + public static final String MULTIPART = "multipart/"; + + + /** + * HTTP content type header for multipart forms. + */ + public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + + + /** + * HTTP content type header for multiple uploads. + */ + public static final String MULTIPART_MIXED = "multipart/mixed"; + + + /** + * The maximum length of a single header line that will be parsed + * (1024 bytes). + */ + public static final int MAX_HEADER_SIZE = 1024; + + + // ----------------------------------------------------------- Data members + + + /** + * The maximum size permitted for an uploaded file. A value of -1 indicates + * no maximum. + */ + private long sizeMax = -1; + + + /** + * The content encoding to use when reading part headers. + */ + private String headerEncoding; + + + // ----------------------------------------------------- Property accessors + + + /** + * Returns the factory class used when creating file items. + * + * @return The factory class for new file items. + */ + public abstract FileItemFactory getFileItemFactory(); + + + /** + * Sets the factory class to use when creating file items. + * + * @param factory The factory class for new file items. + */ + public abstract void setFileItemFactory(FileItemFactory factory); + + + /** + * Returns the maximum allowed upload size. + * + * @return The maximum allowed size, in bytes. + * + * @see #setSizeMax(long) + * + */ + public long getSizeMax() + { + return sizeMax; + } + + + /** + * Sets the maximum allowed upload size. If negative, there is no maximum. + * + * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum. + * + * @see #getSizeMax() + * + */ + public void setSizeMax(long sizeMax) + { + this.sizeMax = sizeMax; + } + + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or null, the platform + * default encoding is used. + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() + { + return headerEncoding; + } + + + /** + * Specifies the character encoding to be used when reading the headers of + * individual parts. When not specified, or null, the platform + * default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(String encoding) + { + headerEncoding = encoding; + } + + + // --------------------------------------------------------- Public methods + + + /** + * Processes an RFC 1867 + * compliant multipart/form-data stream. If files are stored + * on disk, the path is given by getRepository(). + * + * @param req The servlet request to be parsed. + * + * @return A list of FileItem instances parsed from the + * request, in the order that they were transmitted. + * + * @exception FileUploadException if there are problems reading/parsing + * the request or storing files. + */ + public List /* FileItem */ parseRequest(HttpServletRequest req) + throws FileUploadException + { + if (null == req) + { + throw new NullPointerException("req parameter"); + } + + ArrayList items = new ArrayList(); + String contentType = req.getHeader(CONTENT_TYPE); + + if ((null == contentType) || (!contentType.startsWith(MULTIPART))) + { + throw new InvalidContentTypeException( + "the request doesn't contain a " + + MULTIPART_FORM_DATA + + " or " + + MULTIPART_MIXED + + " stream, content type header is " + + contentType); + } + int requestSize = req.getContentLength(); + + if (requestSize == -1) + { + throw new UnknownSizeException( + "the request was rejected because it's size is unknown"); + } + + if (sizeMax >= 0 && requestSize > sizeMax) + { + throw new SizeLimitExceededException( + "the request was rejected because " + + "it's size exceeds allowed range"); + } + + try + { + int boundaryIndex = contentType.indexOf("boundary="); + if (boundaryIndex < 0) + { + throw new FileUploadException( + "the request was rejected because " + + "no multipart boundary was found"); + } + byte[] boundary = contentType.substring( + boundaryIndex + 9).getBytes(); + + InputStream input = req.getInputStream(); + + MultipartStream multi = new MultipartStream(input, boundary); + multi.setHeaderEncoding(headerEncoding); + + boolean nextPart = multi.skipPreamble(); + while (nextPart) + { + Map headers = parseHeaders(multi.readHeaders()); + String fieldName = getFieldName(headers); + if (fieldName != null) + { + String subContentType = getHeader(headers, CONTENT_TYPE); + if (subContentType != null && subContentType + .startsWith(MULTIPART_MIXED)) + { + // Multiple files. + byte[] subBoundary = + subContentType.substring( + subContentType + .indexOf("boundary=") + 9).getBytes(); + multi.setBoundary(subBoundary); + boolean nextSubPart = multi.skipPreamble(); + while (nextSubPart) + { + headers = parseHeaders(multi.readHeaders()); + if (getFileName(headers) != null) + { + FileItem item = + createItem(headers, false); + OutputStream os = item.getOutputStream(); + try + { + multi.readBodyData(os); + } + finally + { + os.close(); + } + items.add(item); + } + else + { + // Ignore anything but files inside + // multipart/mixed. + multi.discardBodyData(); + } + nextSubPart = multi.readBoundary(); + } + multi.setBoundary(boundary); + } + else + { + if (getFileName(headers) != null) + { + // A single file. + FileItem item = createItem(headers, false); + OutputStream os = item.getOutputStream(); + try + { + multi.readBodyData(os); + } + finally + { + os.close(); + } + items.add(item); + } + else + { + // A form field. + FileItem item = createItem(headers, true); + OutputStream os = item.getOutputStream(); + try + { + multi.readBodyData(os); + } + finally + { + os.close(); + } + items.add(item); + } + } + } + else + { + // Skip this part. + multi.discardBodyData(); + } + nextPart = multi.readBoundary(); + } + } + catch (IOException e) + { + throw new FileUploadException( + "Processing of " + MULTIPART_FORM_DATA + + " request failed. " + e.getMessage()); + } + + return items; + } + + + // ------------------------------------------------------ Protected methods + + + /** + * Retrieves the file name from the Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The file name for the current encapsulation. + */ + protected String getFileName(Map /* String, String */ headers) + { + String fileName = null; + String cd = getHeader(headers, CONTENT_DISPOSITION); + if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) + { + int start = cd.indexOf("filename=\""); + int end = cd.indexOf('"', start + 10); + if (start != -1 && end != -1) + { + fileName = cd.substring(start + 10, end).trim(); + } + } + return fileName; + } + + + /** + * Retrieves the field name from the Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The field name for the current encapsulation. + */ + protected String getFieldName(Map /* String, String */ headers) + { + String fieldName = null; + String cd = getHeader(headers, CONTENT_DISPOSITION); + if (cd != null && cd.startsWith(FORM_DATA)) + { + int start = cd.indexOf("name=\""); + int end = cd.indexOf('"', start + 6); + if (start != -1 && end != -1) + { + fieldName = cd.substring(start + 6, end); + } + } + return fieldName; + } + + + /** + * Creates a new {@link FileItem} instance. + * + * @param headers A Map containing the HTTP request + * headers. + * @param isFormField Whether or not this item is a form field, as + * opposed to a file. + * + * @return A newly created FileItem instance. + * + * @exception FileUploadException if an error occurs. + */ + protected FileItem createItem(Map /* String, String */ headers, + boolean isFormField) + throws FileUploadException + { + return getFileItemFactory().createItem(getFieldName(headers), + getHeader(headers, CONTENT_TYPE), + isFormField, + getFileName(headers)); + } + + + /** + *

Parses the header-part and returns as key/value + * pairs. + * + *

If there are multiple headers of the same names, the name + * will map to a comma-separated list containing the values. + * + * @param headerPart The header-part of the current + * encapsulation. + * + * @return A Map containing the parsed HTTP request headers. + */ + protected Map /* String, String */ parseHeaders(String headerPart) + { + Map headers = new HashMap(); + char buffer[] = new char[MAX_HEADER_SIZE]; + boolean done = false; + int j = 0; + int i; + String header, headerName, headerValue; + try + { + while (!done) + { + i = 0; + // Copy a single line of characters into the buffer, + // omitting trailing CRLF. + while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n') + { + buffer[i++] = headerPart.charAt(j++); + } + header = new String(buffer, 0, i - 2); + if (header.equals("")) + { + done = true; + } + else + { + if (header.indexOf(':') == -1) + { + // This header line is malformed, skip it. + continue; + } + headerName = header.substring(0, header.indexOf(':')) + .trim().toLowerCase(); + headerValue = + header.substring(header.indexOf(':') + 1).trim(); + if (getHeader(headers, headerName) != null) + { + // More that one heder of that name exists, + // append to the list. + headers.put(headerName, + getHeader(headers, headerName) + ',' + + headerValue); + } + else + { + headers.put(headerName, headerValue); + } + } + } + } + catch (IndexOutOfBoundsException e) + { + // Headers were malformed. continue with all that was + // parsed. + } + return headers; + } + + + /** + * Returns the header with the specified name from the supplied map. The + * header lookup is case-insensitive. + * + * @param headers A Map containing the HTTP request headers. + * @param name The name of the header to return. + * + * @return The value of specified header, or a comma-separated list if + * there were multiple headers of that name. + */ + protected final String getHeader(Map /* String, String */ headers, + String name) + { + return (String) headers.get(name.toLowerCase()); + } + + + /** + * Thrown to indicate that the request is not a multipart request. + */ + public static class InvalidContentTypeException + extends FileUploadException + { + /** + * Constructs a InvalidContentTypeException with no + * detail message. + */ + public InvalidContentTypeException() + { + super(); + } + + /** + * Constructs an InvalidContentTypeException with + * the specified detail message. + * + * @param message The detail message. + */ + public InvalidContentTypeException(String message) + { + super(message); + } + } + + + /** + * Thrown to indicate that the request size is not specified. + */ + public static class UnknownSizeException + extends FileUploadException + { + /** + * Constructs a UnknownSizeException with no + * detail message. + */ + public UnknownSizeException() + { + super(); + } + + /** + * Constructs an UnknownSizeException with + * the specified detail message. + * + * @param message The detail message. + */ + public UnknownSizeException(String message) + { + super(message); + } + } + + + /** + * Thrown to indicate that the request size exceeds the configured maximum. + */ + public static class SizeLimitExceededException + extends FileUploadException + { + /** + * Constructs a SizeExceededException with no + * detail message. + */ + public SizeLimitExceededException() + { + super(); + } + + /** + * Constructs an SizeExceededException with + * the specified detail message. + * + * @param message The detail message. + */ + public SizeLimitExceededException(String message) + { + super(message); + } + } + +} diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java new file mode 100644 index 000000000..bbb21db60 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +/** + * Exception for errors encountered while processing the request. + * + * @author John McNally + * @version $Id: FileUploadException.java,v 1.7 2003/04/27 17:30:06 martinc Exp $ + */ +public class FileUploadException + extends Exception +{ + + /** + * Constructs a new FileUploadException without message. + */ + public FileUploadException() + { + } + + /** + * Constructs a new FileUploadException with specified detail + * message. + * + * @param msg the error message. + */ + public FileUploadException(String msg) + { + super(msg); + } +} diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java new file mode 100644 index 000000000..97a22d1e3 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java @@ -0,0 +1,891 @@ +/* + * Copyright 2001-2006 The Apache Software Foundation. + * + * Licensed 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.tomcat.util.http.fileupload; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + + +/** + *

Low level API for processing file uploads. + * + *

This class can be used to process data streams conforming to MIME + * 'multipart' format as defined in + * RFC 1867. Arbitrarily + * large amounts of data in the stream can be processed under constant + * memory usage. + * + *

The format of the stream is defined in the following way:
+ * + * + * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boudary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ascii characters except ":">
+ * header-value := <any ascii characters except CR & LF>
+ * body-data := <arbitrary data>
+ *
+ * + *

Note that body-data can contain another mulipart entity. There + * is limited support for single pass processing of such nested + * streams. The nested stream is required to have a + * boundary token of the same length as the parent stream (see {@link + * #setBoundary(byte[])}). + * + *

Here is an exaple of usage of this class.
+ * + *

+ *    try {
+ *        MultipartStream multipartStream = new MultipartStream(input,
+ *                                                              boundary);
+ *        boolean nextPart = malitPartStream.skipPreamble();
+ *        OutputStream output;
+ *        while(nextPart) {
+ *            header = chunks.readHeader();
+ *            // process headers
+ *            // create some output stream
+ *            multipartStream.readBodyPart(output);
+ *            nextPart = multipartStream.readBoundary();
+ *        }
+ *    } catch(MultipartStream.MalformedStreamException e) {
+ *          // the stream failed to follow required syntax
+ *    } catch(IOException) {
+ *          // a read or write error occurred
+ *    }
+ *
+ * 
+ * + * @author Rafal Krzewski + * @author Martin Cooper + * @author Sean C. Sullivan + * + * @version $Id: MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $ + */ +public class MultipartStream +{ + + // ----------------------------------------------------- Manifest constants + + + /** + * The maximum length of header-part that will be + * processed (10 kilobytes = 10240 bytes.). + */ + public static final int HEADER_PART_SIZE_MAX = 10240; + + + /** + * The default length of the buffer used for processing a request. + */ + protected static final int DEFAULT_BUFSIZE = 4096; + + + /** + * A byte sequence that marks the end of header-part + * (CRLFCRLF). + */ + protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A}; + + + /** + * A byte sequence that that follows a delimiter that will be + * followed by an encapsulation (CRLF). + */ + protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A }; + + + /** + * A byte sequence that that follows a delimiter of the last + * encapsulation in the stream (--). + */ + protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D }; + + + // ----------------------------------------------------------- Data members + + + /** + * The input stream from which data is read. + */ + private InputStream input; + + + /** + * The length of the boundary token plus the leading CRLF--. + */ + private int boundaryLength; + + + /** + * The amount of data, in bytes, that must be kept in the buffer in order + * to detect delimiters reliably. + */ + private int keepRegion; + + + /** + * The byte sequence that partitions the stream. + */ + private byte[] boundary; + + + /** + * The length of the buffer used for processing the request. + */ + private int bufSize; + + + /** + * The buffer used for processing the request. + */ + private byte[] buffer; + + + /** + * The index of first valid character in the buffer. + *
+ * 0 <= head < bufSize + */ + private int head; + + + /** + * The index of last valid characer in the buffer + 1. + *
+ * 0 <= tail <= bufSize + */ + private int tail; + + + /** + * The content encoding to use when reading headers. + */ + private String headerEncoding; + + + // ----------------------------------------------------------- Constructors + + + /** + * Default constructor. + * + * @see #MultipartStream(InputStream, byte[], int) + * @see #MultipartStream(InputStream, byte[]) + * + */ + public MultipartStream() + { + } + + + /** + *

Constructs a MultipartStream with a custom size buffer. + * + *

Note that the buffer must be at least big enough to contain the + * boundary string, plus 4 characters for CR/LF and double dash, plus at + * least one byte of data. Too small a buffer size setting will degrade + * performance. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * @param bufSize The size of the buffer to be used, in bytes. + * + * + * @see #MultipartStream() + * @see #MultipartStream(InputStream, byte[]) + * + */ + public MultipartStream(InputStream input, + byte[] boundary, + int bufSize) + { + this.input = input; + this.bufSize = bufSize; + this.buffer = new byte[bufSize]; + + // We prepend CR/LF to the boundary to chop trailng CR/LF from + // body-data tokens. + this.boundary = new byte[boundary.length + 4]; + this.boundaryLength = boundary.length + 4; + this.keepRegion = boundary.length + 3; + this.boundary[0] = 0x0D; + this.boundary[1] = 0x0A; + this.boundary[2] = 0x2D; + this.boundary[3] = 0x2D; + System.arraycopy(boundary, 0, this.boundary, 4, boundary.length); + + head = 0; + tail = 0; + } + + + /** + *

Constructs a MultipartStream with a default size buffer. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * + * @exception IOException when an error occurs. + * + * @see #MultipartStream() + * @see #MultipartStream(InputStream, byte[], int) + * + */ + public MultipartStream(InputStream input, + byte[] boundary) + throws IOException + { + this(input, boundary, DEFAULT_BUFSIZE); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Retrieves the character encoding used when reading the headers of an + * individual part. When not specified, or null, the platform + * default encoding is used. + + * + * @return The encoding used to read part headers. + */ + public String getHeaderEncoding() + { + return headerEncoding; + } + + + /** + * Specifies the character encoding to be used when reading the headers of + * individual parts. When not specified, or null, the platform + * default encoding is used. + * + * @param encoding The encoding used to read part headers. + */ + public void setHeaderEncoding(String encoding) + { + headerEncoding = encoding; + } + + + /** + * Reads a byte from the buffer, and refills it as + * necessary. + * + * @return The next byte from the input stream. + * + * @exception IOException if there is no more data available. + */ + public byte readByte() + throws IOException + { + // Buffer depleted ? + if (head == tail) + { + head = 0; + // Refill. + tail = input.read(buffer, head, bufSize); + if (tail == -1) + { + // No more data available. + throw new IOException("No more data is available"); + } + } + return buffer[head++]; + } + + + /** + * Skips a boundary token, and checks whether more + * encapsulations are contained in the stream. + * + * @return true if there are more encapsulations in + * this stream; false otherwise. + * + * @exception MalformedStreamException if the stream ends unexpecetedly or + * fails to follow required syntax. + */ + public boolean readBoundary() + throws MalformedStreamException + { + byte[] marker = new byte[2]; + boolean nextChunk = false; + + head += boundaryLength; + try + { + marker[0] = readByte(); + marker[1] = readByte(); + if (arrayequals(marker, STREAM_TERMINATOR, 2)) + { + nextChunk = false; + } + else if (arrayequals(marker, FIELD_SEPARATOR, 2)) + { + nextChunk = true; + } + else + { + throw new MalformedStreamException( + "Unexpected characters follow a boundary"); + } + } + catch (IOException e) + { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + return nextChunk; + } + + + /** + *

Changes the boundary token used for partitioning the stream. + * + *

This method allows single pass processing of nested multipart + * streams. + * + *

The boundary token of the nested stream is required + * to be of the same length as the boundary token in parent stream. + * + *

Restoring the parent stream boundary token after processing of a + * nested stream is left to the application. + * + * @param boundary The boundary to be used for parsing of the nested + * stream. + * + * @exception IllegalBoundaryException if the boundary + * has a different length than the one + * being currently parsed. + */ + public void setBoundary(byte[] boundary) + throws IllegalBoundaryException + { + if (boundary.length != boundaryLength - 4) + { + throw new IllegalBoundaryException( + "The length of a boundary token can not be changed"); + } + System.arraycopy(boundary, 0, this.boundary, 4, boundary.length); + } + + + /** + *

Reads the header-part of the current + * encapsulation. + * + *

Headers are returned verbatim to the input stream, including the + * trailing CRLF marker. Parsing is left to the + * application. + * + *

TODO allow limiting maximum header size to + * protect against abuse. + * + * @return The header-part of the current encapsulation. + * + * @exception MalformedStreamException if the stream ends unexpecetedly. + */ + public String readHeaders() + throws MalformedStreamException + { + int i = 0; + byte b[] = new byte[1]; + // to support multi-byte characters + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int sizeMax = HEADER_PART_SIZE_MAX; + int size = 0; + while (i < 4) + { + try + { + b[0] = readByte(); + } + catch (IOException e) + { + throw new MalformedStreamException("Stream ended unexpectedly"); + } + size++; + if (b[0] == HEADER_SEPARATOR[i]) + { + i++; + } + else + { + i = 0; + } + if (size <= sizeMax) + { + baos.write(b[0]); + } + } + + String headers = null; + if (headerEncoding != null) + { + try + { + headers = baos.toString(headerEncoding); + } + catch (UnsupportedEncodingException e) + { + // Fall back to platform default if specified encoding is not + // supported. + headers = baos.toString(); + } + } + else + { + headers = baos.toString(); + } + + return headers; + } + + + /** + *

Reads body-data from the current + * encapsulation and writes its contents into the + * output Stream. + * + *

Arbitrary large amounts of data can be processed by this + * method using a constant size buffer. (see {@link + * #MultipartStream(InputStream,byte[],int) constructor}). + * + * @param output The Stream to write data into. + * + * @return the amount of data written. + * + * @exception MalformedStreamException if the stream ends unexpectedly. + * @exception IOException if an i/o error occurs. + */ + public int readBodyData(OutputStream output) + throws MalformedStreamException, + IOException + { + boolean done = false; + int pad; + int pos; + int bytesRead; + int total = 0; + while (!done) + { + // Is boundary token present somewere in the buffer? + pos = findSeparator(); + if (pos != -1) + { + // Write the rest of the data before the boundary. + output.write(buffer, head, pos - head); + total += pos - head; + head = pos; + done = true; + } + else + { + // Determine how much data should be kept in the + // buffer. + if (tail - head > keepRegion) + { + pad = keepRegion; + } + else + { + pad = tail - head; + } + // Write out the data belonging to the body-data. + output.write(buffer, head, tail - head - pad); + + // Move the data to the beging of the buffer. + total += tail - head - pad; + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // Refill buffer with new data. + head = 0; + bytesRead = input.read(buffer, pad, bufSize - pad); + + // [pprrrrrrr] + if (bytesRead != -1) + { + tail = pad + bytesRead; + } + else + { + // The last pad amount is left in the buffer. + // Boundary can't be in there so write out the + // data you have and signal an error condition. + output.write(buffer, 0, pad); + output.flush(); + total += pad; + throw new MalformedStreamException( + "Stream ended unexpectedly"); + } + } + } + output.flush(); + return total; + } + + + /** + *

Reads body-data from the current + * encapsulation and discards it. + * + *

Use this method to skip encapsulations you don't need or don't + * understand. + * + * @return The amount of data discarded. + * + * @exception MalformedStreamException if the stream ends unexpectedly. + * @exception IOException if an i/o error occurs. + */ + public int discardBodyData() + throws MalformedStreamException, + IOException + { + boolean done = false; + int pad; + int pos; + int bytesRead; + int total = 0; + while (!done) + { + // Is boundary token present somewere in the buffer? + pos = findSeparator(); + if (pos != -1) + { + // Write the rest of the data before the boundary. + total += pos - head; + head = pos; + done = true; + } + else + { + // Determine how much data should be kept in the + // buffer. + if (tail - head > keepRegion) + { + pad = keepRegion; + } + else + { + pad = tail - head; + } + total += tail - head - pad; + + // Move the data to the beging of the buffer. + System.arraycopy(buffer, tail - pad, buffer, 0, pad); + + // Refill buffer with new data. + head = 0; + bytesRead = input.read(buffer, pad, bufSize - pad); + + // [pprrrrrrr] + if (bytesRead != -1) + { + tail = pad + bytesRead; + } + else + { + // The last pad amount is left in the buffer. + // Boundary can't be in there so signal an error + // condition. + total += pad; + throw new MalformedStreamException( + "Stream ended unexpectedly"); + } + } + } + return total; + } + + + /** + * Finds the beginning of the first encapsulation. + * + * @return true if an encapsulation was found in + * the stream. + * + * @exception IOException if an i/o error occurs. + */ + public boolean skipPreamble() + throws IOException + { + // First delimiter may be not preceeded with a CRLF. + System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2); + boundaryLength = boundary.length - 2; + try + { + // Discard all data up to the delimiter. + discardBodyData(); + + // Read boundary - if succeded, the stream contains an + // encapsulation. + return readBoundary(); + } + catch (MalformedStreamException e) + { + return false; + } + finally + { + // Restore delimiter. + System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2); + boundaryLength = boundary.length; + boundary[0] = 0x0D; + boundary[1] = 0x0A; + } + } + + + /** + * Compares count first bytes in the arrays + * a and b. + * + * @param a The first array to compare. + * @param b The second array to compare. + * @param count How many bytes should be compared. + * + * @return true if count first bytes in arrays + * a and b are equal. + */ + public static boolean arrayequals(byte[] a, + byte[] b, + int count) + { + for (int i = 0; i < count; i++) + { + if (a[i] != b[i]) + { + return false; + } + } + return true; + } + + + /** + * Searches for a byte of specified value in the buffer, + * starting at the specified position. + * + * @param value The value to find. + * @param pos The starting position for searching. + * + * @return The position of byte found, counting from beginning of the + * buffer, or -1 if not found. + */ + protected int findByte(byte value, + int pos) + { + for (int i = pos; i < tail; i++) + { + if (buffer[i] == value) + { + return i; + } + } + + return -1; + } + + + /** + * Searches for the boundary in the buffer + * region delimited by head and tail. + * + * @return The position of the boundary found, counting from the + * beginning of the buffer, or -1 if + * not found. + */ + protected int findSeparator() + { + int first; + int match = 0; + int maxpos = tail - boundaryLength; + for (first = head; + (first <= maxpos) && (match != boundaryLength); + first++) + { + first = findByte(boundary[0], first); + if (first == -1 || (first > maxpos)) + { + return -1; + } + for (match = 1; match < boundaryLength; match++) + { + if (buffer[first + match] != boundary[match]) + { + break; + } + } + } + if (match == boundaryLength) + { + return first - 1; + } + return -1; + } + + /** + * Returns a string representation of this object. + * + * @return The string representation of this object. + */ + public String toString() + { + StringBuffer sbTemp = new StringBuffer(); + sbTemp.append("boundary='"); + sbTemp.append(String.valueOf(boundary)); + sbTemp.append("'\nbufSize="); + sbTemp.append(bufSize); + return sbTemp.toString(); + } + + /** + * Thrown to indicate that the input stream fails to follow the + * required syntax. + */ + public class MalformedStreamException + extends IOException + { + /** + * Constructs a MalformedStreamException with no + * detail message. + */ + public MalformedStreamException() + { + super(); + } + + /** + * Constructs an MalformedStreamException with + * the specified detail message. + * + * @param message The detail message. + */ + public MalformedStreamException(String message) + { + super(message); + } + } + + + /** + * Thrown upon attempt of setting an invalid boundary token. + */ + public class IllegalBoundaryException + extends IOException + { + /** + * Constructs an IllegalBoundaryException with no + * detail message. + */ + public IllegalBoundaryException() + { + super(); + } + + /** + * Constructs an IllegalBoundaryException with + * the specified detail message. + * + * @param message The detail message. + */ + public IllegalBoundaryException(String message) + { + super(message); + } + } + + + // ------------------------------------------------------ Debugging methods + + + // These are the methods that were used to debug this stuff. + /* + + // Dump data. + protected void dump() + { + System.out.println("01234567890"); + byte[] temp = new byte[buffer.length]; + for(int i=0; i + * This class overrides all OutputStream methods. However, these + * overrides ultimately call the corresponding methods in the underlying output + * stream implementation. + *

+ * NOTE: This implementation may trigger the event before the threshold + * is actually reached, since it triggers when a pending write operation would + * cause the threshold to be exceeded. + * + * @author Martin Cooper + * + * @version $Id: ThresholdingOutputStream.java,v 1.3 2003/05/31 22:31:08 martinc Exp $ + */ +public abstract class ThresholdingOutputStream + extends OutputStream +{ + + // ----------------------------------------------------------- Data members + + + /** + * The threshold at which the event will be triggered. + */ + private int threshold; + + + /** + * The number of bytes written to the output stream. + */ + private long written; + + + /** + * Whether or not the configured threshold has been exceeded. + */ + private boolean thresholdExceeded; + + + // ----------------------------------------------------------- Constructors + + + /** + * Constructs an instance of this class which will trigger an event at the + * specified threshold. + * + * @param threshold The number of bytes at which to trigger an event. + */ + public ThresholdingOutputStream(int threshold) + { + this.threshold = threshold; + } + + + // --------------------------------------------------- OutputStream methods + + + /** + * Writes the specified byte to this output stream. + * + * @param b The byte to be written. + * + * @exception IOException if an error occurs. + */ + public void write(int b) throws IOException + { + checkThreshold(1); + getStream().write(b); + written++; + } + + + /** + * Writes b.length bytes from the specified byte array to this + * output stream. + * + * @param b The array of bytes to be written. + * + * @exception IOException if an error occurs. + */ + public void write(byte b[]) throws IOException + { + checkThreshold(b.length); + getStream().write(b); + written += b.length; + } + + + /** + * Writes len bytes from the specified byte array starting at + * offset off to this output stream. + * + * @param b The byte array from which the data will be written. + * @param off The start offset in the byte array. + * @param len The number of bytes to write. + * + * @exception IOException if an error occurs. + */ + public void write(byte b[], int off, int len) throws IOException + { + checkThreshold(len); + getStream().write(b, off, len); + written += len; + } + + + /** + * Flushes this output stream and forces any buffered output bytes to be + * written out. + * + * @exception IOException if an error occurs. + */ + public void flush() throws IOException + { + getStream().flush(); + } + + + /** + * Closes this output stream and releases any system resources associated + * with this stream. + * + * @exception IOException if an error occurs. + */ + public void close() throws IOException + { + try + { + flush(); + } + catch (IOException ignored) + { + // ignore + } + getStream().close(); + } + + + // --------------------------------------------------------- Public methods + + + /** + * Returns the threshold, in bytes, at which an event will be triggered. + * + * @return The threshold point, in bytes. + */ + public int getThreshold() + { + return threshold; + } + + + /** + * Returns the number of bytes that have been written to this output stream. + * + * @return The number of bytes written. + */ + public long getByteCount() + { + return written; + } + + + /** + * Determines whether or not the configured threshold has been exceeded for + * this output stream. + * + * @return true if the threshold has been reached; + * false otherwise. + */ + public boolean isThresholdExceeded() + { + return (written > threshold); + } + + + // ------------------------------------------------------ Protected methods + + + /** + * Checks to see if writing the specified number of bytes would cause the + * configured threshold to be exceeded. If so, triggers an event to allow + * a concrete implementation to take action on this. + * + * @param count The number of bytes about to be written to the underlying + * output stream. + * + * @exception IOException if an error occurs. + */ + protected void checkThreshold(int count) throws IOException + { + if (!thresholdExceeded && (written + count > threshold)) + { + thresholdReached(); + thresholdExceeded = true; + } + } + + + // ------------------------------------------------------- Abstract methods + + + /** + * Returns the underlying output stream, to which the corresponding + * OutputStream methods in this class will ultimately delegate. + * + * @return The underlying output stream. + * + * @exception IOException if an error occurs. + */ + protected abstract OutputStream getStream() throws IOException; + + + /** + * Indicates that the configured threshold has been reached, and that a + * subclass should take whatever action necessary on this event. This may + * include changing the underlying output stream. + * + * @exception IOException if an error occurs. + */ + protected abstract void thresholdReached() throws IOException; +} diff --git a/java/org/apache/tomcat/util/http/fileupload/package.html b/java/org/apache/tomcat/util/http/fileupload/package.html new file mode 100644 index 000000000..853871f15 --- /dev/null +++ b/java/org/apache/tomcat/util/http/fileupload/package.html @@ -0,0 +1,66 @@ + + + + Overview of the org.apache.commons.fileupload component + + +

+ Component for handling html file uploads as given by rfc 1867 + RFC 1867. +

+

+ Normal usage of the package involves + {@link org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} + parsing the HttpServletRequest and returning a list of + {@link org.apache.commons.fileupload.FileItem FileItem}'s. + These FileItem's provide easy access to the data + given in the upload. There is also a low level api for + manipulating the upload data encapsulated in the + {@link org.apache.commons.fileupload.MultipartStream MultipartStream} + class. +

+ +

+ Normal usage example: +

+
+
+    public void doPost(HttpServletRequest req, HttpServletResponse res)
+    {
+        DiskFileUpload fu = new DiskFileUpload();
+        // maximum size before a FileUploadException will be thrown
+        fu.setSizeMax(1000000);
+        // maximum size that will be stored in memory
+        fu.setSizeThreshold(4096);
+        // the location for saving data that is larger than getSizeThreshold()
+        fu.setRepositoryPath("/tmp");
+
+        List fileItems = fu.parseRequest(req);
+        // assume we know there are two files. The first file is a small
+        // text file, the second is unknown and is written to a file on
+        // the server
+        Iterator i = fileItems.iterator();
+        String comment = ((FileItem)i.next()).getString();
+        FileItem fi = (FileItem)i.next();
+        // filename on the client
+        String fileName = fi.getName();
+        // save comment and filename to database
+        ...
+        // write the file
+        fi.write("/www/uploads/" + fileName);
+    }
+
+

+ In the example above the first file is loaded into memory as a + String. Before calling the getString method, the data + may have been in memory or on disk depending on its size. The second + file we assume it will be large and therefore never explicitly load + it into memory, though if it is less than 4096 bytes it will be + in memory before it is written to its final location. When writing to + the final location, if the data is larger than the + threshold, an attempt is made to rename the temporary file to + the given location. If it cannot be renamed, it is streamed to the + new location. +

+ + -- 2.11.0