/** * $RCSfile$ * $Revision$ * $Date$ * * Copyright 2005 Jive Software. * * All rights reserved. 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.jivesoftware.whack.container; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.jivesoftware.whack.ExternalComponentManager; import org.xmpp.component.Component; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * The component servlet acts as a proxy for web requests (in the web admin) * to components. Since components can be dynamically loaded and live in a different place * than normal Whack admin console files, it's not possible to have them * added to the normal Whack admin console web app directory.<p> * <p/> * The servlet listens for requests in the form <tt>/components/[componentName]/[JSP File]</tt> * (e.g. <tt>/components/foo/example.jsp</tt>). It also listens for image requests in the * the form <tt>/components/[componentName]/images/*.png|gif</tt> (e.g. * <tt>/components/foo/images/example.gif</tt>).<p> * <p/> * JSP files must be compiled and available via the component's class loader. The mapping * between JSP name and servlet class files is defined in [componentName]/web/web.xml. * Typically, this file is auto-generated by the JSP compiler when packaging the component. * * @author Matt Tucker * @author Gaston Dombiak */ public class ComponentServlet extends HttpServlet { private static Map<String, HttpServlet> servlets; private static File componentDirectory; private static ServletConfig servletConfig; private static ExternalComponentManager manager; static { servlets = new ConcurrentHashMap<String, HttpServlet>(); componentDirectory = new File(ServerContainer.getInstance().getHomeDirectory(), "components"); manager = (ExternalComponentManager) ServerContainer.getInstance().getManager(); } public void init(ServletConfig config) throws ServletException { super.init(config); servletConfig = config; } public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pathInfo = request.getPathInfo(); if (pathInfo == null) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } else { try { // Handle JSP requests. if (pathInfo.endsWith(".jsp")) { handleJSP(pathInfo, request, response); return; } // Handle image requests. else if (pathInfo.endsWith(".gif") || pathInfo.endsWith(".png")) { handleImage(pathInfo, response); return; } // Handle servlet requests. else if (servlets.containsKey(pathInfo.substring(1).toLowerCase())) { handleServlet(pathInfo, request, response); } // Anything else results in a 404. else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } } catch (Exception e) { manager.getLog().error(e); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } } } /** * Registers all JSP page servlets for a component. * * @param finder the component finder. * @param component the component. * @param webXML the web.xml file containing JSP page names to servlet class file * mappings. */ public static void registerServlets(ComponentFinder finder, Component component, File webXML) { if (!webXML.exists()) { manager.getLog().error("Could not register component servlets, file " + webXML.getAbsolutePath() + " does not exist."); return; } // Find the name of the component directory given that the webXML file // lives in plugins/[pluginName]/web/web.xml String pluginName = webXML.getParentFile().getParentFile().getName(); try { // Make the reader non-validating so that it doesn't try to resolve external // DTD's. Trying to resolve external DTD's can break on some firewall configurations. SAXReader saxReader = new SAXReader(false); saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = saxReader.read(webXML); // Find all <servlet> entries to discover name to class mapping. List classes = doc.selectNodes("//servlet"); Map<String, Class> classMap = new HashMap<String, Class>(); for (int i = 0; i < classes.size(); i++) { Element servletElement = (Element)classes.get(i); String name = servletElement.element("servlet-name").getTextTrim(); String className = servletElement.element("servlet-class").getTextTrim(); classMap.put(name, finder.loadClass(className, component)); } // Find all <servelt-mapping> entries to discover name to URL mapping. List names = doc.selectNodes("//servlet-mapping"); for (int i = 0; i < names.size(); i++) { Element nameElement = (Element)names.get(i); String name = nameElement.element("servlet-name").getTextTrim(); String url = nameElement.element("url-pattern").getTextTrim(); // Register the servlet for the URL. Class servletClass = classMap.get(name); Object instance = servletClass.newInstance(); if (instance instanceof HttpServlet) { // Initialize the servlet then add it to the map.. ((HttpServlet)instance).init(servletConfig); servlets.put(pluginName + url, (HttpServlet)instance); } else { manager.getLog().warn("Could not load " + (pluginName + url) + ": not a servlet."); } } } catch (Throwable e) { manager.getLog().error(e); } } /** * Unregisters all JSP page servlets for a component. * * @param webXML the web.xml file containing JSP page names to servlet class file * mappings. */ public static void unregisterServlets(File webXML) { if (!webXML.exists()) { manager.getLog().error("Could not unregister component servlets, file " + webXML.getAbsolutePath() + " does not exist."); return; } // Find the name of the component directory given that the webXML file // lives in plugins/[pluginName]/web/web.xml String pluginName = webXML.getParentFile().getParentFile().getName(); try { SAXReader saxReader = new SAXReader(false); saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = saxReader.read(webXML); // Find all <servelt-mapping> entries to discover name to URL mapping. List names = doc.selectNodes("//servlet-mapping"); for (int i = 0; i < names.size(); i++) { Element nameElement = (Element)names.get(i); String url = nameElement.element("url-pattern").getTextTrim(); // Destroy the servlet than remove from servlets map. HttpServlet servlet = servlets.get(pluginName + url); servlet.destroy(); servlets.remove(pluginName + url); servlet = null; } } catch (Throwable e) { manager.getLog().error(e); } } /** * Handles a request for a JSP page. It checks to see if a servlet is mapped * for the JSP URL. If one is found, request handling is passed to it. If no * servlet is found, a 404 error is returned. * * @param pathInfo the extra path info. * @param request the request object. * @param response the response object. * @throws ServletException if a servlet exception occurs while handling the * request. * @throws IOException if an IOException occurs while handling the request. */ private void handleJSP(String pathInfo, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Strip the starting "/" from the path to find the JSP URL. String jspURL = pathInfo.substring(1); HttpServlet servlet = servlets.get(jspURL); if (servlet != null) { servlet.service(request, response); return; } else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } } /** * Handles a request for a Servlet. If one is found, request handling is passed to it. If no * servlet is found, a 404 error is returned. * * @param pathInfo the extra path info. * @param request the request object. * @param response the response object. * @throws ServletException if a servlet exception occurs while handling the * request. * @throws IOException if an IOException occurs while handling the request. */ private void handleServlet(String pathInfo, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Strip the starting "/" from the path to find the JSP URL. String jspURL = pathInfo.substring(1); HttpServlet servlet = servlets.get(jspURL); if (servlet != null) { servlet.service(request, response); return; } else { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } } /** * Handles a request for an image. * * @param pathInfo the extra path info. * @param response the response object. * @throws IOException if an IOException occurs while handling the request. */ private void handleImage(String pathInfo, HttpServletResponse response) throws IOException { String[] parts = pathInfo.split("/"); // Image request must be in correct format. if (parts.length != 4) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } File image = new File(componentDirectory, parts[1] + File.separator + "web" + File.separator + "images" + File.separator + parts[3]); if (!image.exists()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } else { // Content type will be GIF or PNG. String contentType = "image/gif"; if (pathInfo.endsWith(".png")) { contentType = "image/png"; } response.setHeader("Content-disposition", "filename=\"" + image + "\";"); response.setContentType(contentType); // Write out the image to the user. InputStream in = null; ServletOutputStream out = null; try { in = new BufferedInputStream(new FileInputStream(image)); out = response.getOutputStream(); // Set the size of the file. response.setContentLength((int)image.length()); // Use a 1K buffer. byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) != -1) { out.write(buf, 0, len); } } finally { try { in.close(); } catch (Exception ignored) { } try { out.close(); } catch (Exception ignored) { } } } } }