Java tutorial
/* * Weblounge: Web Content Management System * Copyright (c) 2003 - 2011 The Weblounge Team * http://entwinemedia.com/weblounge * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package ch.entwine.weblounge.kernel.site; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import ch.entwine.weblounge.common.Times; import ch.entwine.weblounge.common.impl.request.Http11ProtocolHandler; import ch.entwine.weblounge.common.impl.request.Http11ResponseType; import ch.entwine.weblounge.common.impl.request.RequestUtils; import ch.entwine.weblounge.common.impl.request.SiteRequestWrapper; import ch.entwine.weblounge.common.impl.request.WebloungeRequestImpl; import ch.entwine.weblounge.common.impl.request.WebloungeResponseImpl; import ch.entwine.weblounge.common.request.WebloungeRequest; import ch.entwine.weblounge.common.security.SecurityService; import ch.entwine.weblounge.common.site.Environment; import ch.entwine.weblounge.common.site.Site; import ch.entwine.weblounge.common.url.UrlUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.tika.Tika; import org.ops4j.pax.web.jsp.JspServletWrapper; import org.osgi.framework.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet that knows how to deal with resources loaded from <code>OSGi</code> * context. */ public class SiteServlet extends HttpServlet { /** The serial version UID */ private static final long serialVersionUID = 6443055837961417300L; /** The logging facility */ private static final Logger logger = LoggerFactory.getLogger(SiteServlet.class); /** The supported formats */ public enum Format { Processed, Raw }; /** Parameter name for the output format */ public static final String PARAM_FORMAT = "format"; /** The site */ private final Site site; /** The site bundle */ private final Bundle bundle; /** The Jasper servlet */ protected final Servlet jasperServlet; /** Path rules */ private List<ResourceSet> resources = null; /** The security service */ private SecurityService securityService = null; /** Tika mime type library */ private Tika tika = null; /** Flag to reflect servlet initialization */ private boolean initialized = false; /** The environment */ private Environment environment = Environment.Any; /** * Creates a new site servlet for the given bundle and context. * * @param site * the site * @param bundle * the site bundle * @param bundle * the site bundle * @param environment * the environment */ public SiteServlet(final Site site, final Bundle bundle, Environment environment) { this.site = site; this.bundle = bundle; this.environment = environment; this.jasperServlet = new JspServletWrapper(bundle); this.resources = new ArrayList<ResourceSet>(); this.resources.add(new SiteResourceSet(site)); this.resources.add(new ModuleResourceSet()); this.tika = new Tika(); } /** * Delegates to the jasper servlet with a controlled context class loader. * * @see JspServletWrapper#init(ServletConfig) */ @Override public void init(final ServletConfig config) throws ServletException { jasperServlet.init(config); initialized = true; } /** * Returns <code>true</code> if the servlet has been initialized. * * @return <code>true</code> if the servlet has been initialized */ public boolean isInitialized() { return initialized; } /** * Sets the environment. * * @param environment * the environment */ public void setEnvironment(Environment environment) { this.environment = environment; } /** * Returns the site that is serving content through this servlet. * * @return the site */ public Site getSite() { return site; } /** * Returns the site's bundle. * * @return the bundle */ public Bundle getBundle() { return bundle; } /** * Delegates to the jasper servlet. * * @see JspServletWrapper#getServletConfig() */ @Override public ServletConfig getServletConfig() { return jasperServlet.getServletConfig(); } /** * Depending on whether a call to a jsp is made or not, delegates to the * jasper servlet with a controlled context class loader or tries to load the * requested file from the bundle as a static resource. * * @see HttpServlet#service(HttpServletRequest, HttpServletResponse) */ @Override public void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { String filename = FilenameUtils.getName(request.getPathInfo()); // Don't allow listing the root directory? if (StringUtils.isBlank(filename)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } // Check the requested format. In case of a JSP, this can either be // processed (default) or raw, in which case the file contents are // returned rather than Jasper's output of it. Format format = Format.Processed; String f = request.getParameter(PARAM_FORMAT); if (StringUtils.isNotBlank(f)) { try { format = Format.valueOf(StringUtils.capitalize(f.toLowerCase())); } catch (IllegalArgumentException e) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } } if (Format.Processed.equals(format) && filename.endsWith(".jsp")) { serviceJavaServerPage(request, response); } else { serviceResource(request, response); } } /** * Delegates to jasper servlet with a controlled context class loader. * * @see JspServletWrapper#service(HttpServletRequest, HttpServletResponse) */ public void serviceJavaServerPage(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) throws ServletException, IOException { final HttpServletRequest request; final HttpServletResponse response; boolean originalRequest = true; // Wrap request and response if necessary if (httpRequest instanceof SiteRequestWrapper) { request = httpRequest; response = httpResponse; originalRequest = false; } else if (httpRequest instanceof WebloungeRequest) { request = new SiteRequestWrapper((WebloungeRequest) httpRequest, httpRequest.getPathInfo(), false); response = httpResponse; originalRequest = false; } else { WebloungeRequestImpl webloungeRequest = new WebloungeRequestImpl(httpRequest, environment); webloungeRequest.init(site); webloungeRequest.setUser(securityService.getUser()); String requestPath = UrlUtils.concat("/site", httpRequest.getPathInfo()); request = new SiteRequestWrapper(webloungeRequest, requestPath, false); response = new WebloungeResponseImpl(httpResponse); ((WebloungeResponseImpl) response).setRequest(webloungeRequest); } // Make sure the resource exists, Jasper will not produce a meaningful error // message, but a PWC6117: File "null" not found String requestPath = request.getRequestURI(); if (bundle.getEntry(requestPath) == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // Configure request and response objects try { jasperServlet.service(request, response); if (originalRequest) { ((WebloungeResponseImpl) response).endResponse(); response.flushBuffer(); } } catch (ServletException e) { throw e; } catch (IOException e) { throw e; } catch (Throwable t) { // Don't log errors during precompilation if (!RequestUtils.isPrecompileRequest(request)) logger.error("Error while serving jsp {}: {}", request.getRequestURI(), t.getMessage()); response.sendError(SC_INTERNAL_SERVER_ERROR, t.getMessage()); } } /** * Tries to serve the request as a static resource from the bundle. * * @param request * the http servlet request * @param response * the http servlet response * @throws ServletException * if serving the request fails * @throws IOException * if writing the response back to the client fails */ protected void serviceResource(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { Http11ResponseType responseType = null; String requestPath = request.getPathInfo(); // There is also a special set of resources that we don't want to expose if (isProtected(requestPath)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String bundlePath = UrlUtils.concat("/site", requestPath); // Does the resource exist? final URL url = bundle.getResource(bundlePath); if (url == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // Load the resource from the bundle URLConnection connection = url.openConnection(); String contentEncoding = connection.getContentEncoding(); long contentLength = connection.getContentLength(); long lastModified = connection.getLastModified(); if (contentLength <= 0) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // final Resource resource = Resource.newResource(url); // if (!resource.exists()) { // response.sendError(HttpServletResponse.SC_NOT_FOUND); // return; // } // We don't allow directory listings // if (resource.isDirectory()) { // response.sendError(HttpServletResponse.SC_FORBIDDEN); // return; // } String mimeType = tika.detect(bundlePath); // Try to get mime type and content encoding from resource if (mimeType == null) mimeType = connection.getContentType(); if (mimeType != null) { if (contentEncoding != null) mimeType += ";" + contentEncoding; response.setContentType(mimeType); } // Send the response back to the client InputStream is = connection.getInputStream(); // InputStream is = resource.getInputStream(); try { logger.debug("Serving {}", url); responseType = Http11ProtocolHandler.analyzeRequest(request, lastModified, Times.MS_PER_DAY + System.currentTimeMillis(), contentLength); if (!Http11ProtocolHandler.generateResponse(response, responseType, is)) { logger.warn("I/O error while generating content from {}", url); } } finally { IOUtils.closeQuietly(is); } } /** * Returns <code>true</code> if the resource is protected. Examples of * protected resources are <code>web.xml</code> inside of the * <code>WEB-INF</code> directory etc. * * @param path * the path to the resource that is about to be served * @return <code>true</code> if the resource needs to be protected */ public boolean isProtected(String path) { for (ResourceSet resourceSet : resources) { if (resourceSet.includes(path) && resourceSet.excludes(path)) return true; } return false; } /** * Delegates to jasper servlet. * * @see JspServletWrapper#getServletInfo() */ @Override public String getServletInfo() { return jasperServlet.getServletInfo(); } /** * Delegates to jasper servlet with a controlled context class loader. * * @see JspServletWrapper#destroy() */ @Override public void destroy() { jasperServlet.destroy(); } /** * Sets the security service. * * @param securityService * the security service */ void setSecurityService(SecurityService securityService) { this.securityService = securityService; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return "Site " + site; } }