/**
* $RCSfile$
* $Revision: 2608 $
* $Date: 2005-04-12 00:15:21 -0700 (Tue, 12 Apr 2005) $
*
* 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.xmpp.component.Component;
import org.xmpp.component.ComponentManager;
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 ComponentManager manager;
static {
servlets = new ConcurrentHashMap<String, HttpServlet>();
componentDirectory = new File(ServerContainer.getInstance().getHomeDirectory(), "components");
manager = 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) {
}
}
}
}
}