/* * RequestProcessor.java * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Copyright (c) 2004 Per Cederberg. All rights reserved. */ package org.liquidsite.app.servlet; import java.io.File; import org.liquidsite.app.template.Template; import org.liquidsite.app.template.TemplateException; import org.liquidsite.app.template.TemplateManager; import org.liquidsite.core.content.Content; import org.liquidsite.core.content.ContentDocument; import org.liquidsite.core.content.ContentException; import org.liquidsite.core.content.ContentFile; import org.liquidsite.core.content.ContentFolder; import org.liquidsite.core.content.ContentForum; import org.liquidsite.core.content.ContentManager; import org.liquidsite.core.content.ContentPage; import org.liquidsite.core.content.ContentSection; import org.liquidsite.core.content.ContentSecurityException; import org.liquidsite.core.content.ContentSite; import org.liquidsite.core.content.ContentTopic; import org.liquidsite.core.content.ContentTranslator; import org.liquidsite.core.content.Domain; import org.liquidsite.core.content.User; import org.liquidsite.core.web.Request; import org.liquidsite.util.log.Log; /** * A request processor. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.0 */ public abstract class RequestProcessor { /** * The class logger. */ private static final Log LOG = new Log(RequestProcessor.class); /** * The content manager to use. */ private ContentManager manager; /** * The base directory for application files. */ private File baseDir; /** * Creates a new request processor. * * @param manager the content manager to use * @param baseDir the base directory for application files */ public RequestProcessor(ContentManager manager, File baseDir) { this.manager = manager; this.baseDir = baseDir; } /** * Returns the content manager used by this processor. * * @return the content manager used by this processor */ protected ContentManager getContentManager() { return manager; } /** * Returns an application file. The file path should be specified * relative to the base application directory. * * @param path the relative file path * * @return the absolute file path */ protected File getFile(String path) { return new File(baseDir, path); } /** * Processes a request. * * @param request the request to process * * @throws RequestException if the request couldn't be processed */ public abstract void process(Request request) throws RequestException; /** * Destroys this request processor. This method frees all * internal resources used by this processor. */ public abstract void destroy(); /** * Processes a request for a normal resource in a content site. * If the preview flag is set a special "revision" request * parameter may be used to modify the content object to display. * * @param request the request object * @param site the content site object * @param path the request path (inside the site) * @param preview the preview flag * * @throws RequestException if the request couldn't be processed */ protected void processNormal(Request request, ContentSite site, String path, boolean preview) throws RequestException { Content content; String str; try { content = locateContent(request, site, path); if (preview) { str = request.getParameter("revision"); if (content != null && str != null) { content = content.getRevision(Integer.parseInt(str)); } } sendContent(request, content); } catch (ContentException e) { LOG.error(e.getMessage()); throw RequestException.INTERNAL_ERROR; } catch (ContentSecurityException e) { LOG.info(e.getMessage()); throw RequestException.FORBIDDEN; } catch (TemplateException e) { if (preview) { request.setAttribute("heading", "Template/Page Error"); str = "An error was found in the page or template. See the " + "detailed error message below."; request.setAttribute("text", str); request.setAttribute("detail", e.getMessage()); try { sendTemplate(request, "error.ftl"); } catch (TemplateException ex) { LOG.error(e.getMessage()); LOG.error(ex.getMessage()); throw RequestException.INTERNAL_ERROR; } } else { LOG.error(e.getMessage()); throw RequestException.INTERNAL_ERROR; } } } /** * Processes a request for the special "/liquidsite" path at the * root of each site. * * @param request the request object * @param path the request path (inside the directory) * * @throws RequestException if the request couldn't be processed */ protected void processLiquidSite(Request request, String path) throws RequestException { Content content; if (path.equals("system/style.css")) { request.sendFile(getFile(path.substring(7)), false); } else if (path.startsWith("system/images/")) { request.sendFile(getFile(path.substring(7)), false); } else if (path.startsWith("content/")) { try { content = locateContent(request, path.substring(8)); sendContent(request, content); } catch (ContentException e) { LOG.error(e.getMessage()); throw RequestException.INTERNAL_ERROR; } catch (ContentSecurityException e) { LOG.info(e.getMessage()); throw RequestException.FORBIDDEN; } catch (TemplateException e) { LOG.error(e.getMessage()); throw RequestException.INTERNAL_ERROR; } } else { throw RequestException.RESOURCE_NOT_FOUND; } } /** * Finds the content object corresponding to an object request * path. Note that this method may return any type of content * object as long as it supports being presented by the * sendContent() method. The object request path should start * with a content id, optionally followed by a slash and the name * of a child object. This method also checks the request domain * to make sure that the content object returned belongs to the * same domain as the site it is requested through. * * @param request the request object * @param path the content request path * * @return the content object corresponding to the path, or * null if no matching content was found * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the specified content * object wasn't readable by the user */ private Content locateContent(Request request, String path) throws ContentException, ContentSecurityException { User user = request.getUser(); String number; String name; Domain domain; Content content; int id; int pos; pos = path.indexOf("/"); if (pos < 0) { number = path; name = null; } else { number = path.substring(0, pos); name = path.substring(pos + 1); } try { id = Integer.parseInt(number); } catch (NumberFormatException e) { return null; } domain = request.getEnvironment().getDomain(); content = manager.getContent(user, id); if (content == null || !domain.equals(content.getDomain())) { return null; } if (name == null || name.equals("")) { return content; } else { return locateChild(request, content, name); } } /** * Finds the content page corresponding to a request path. Note * that this method may return any type of content object as long * as it supports being presented by the sendContent() method. * This method may set parts of the request environment while * processing the path. * * @param request the request object * @param site the content site * @param path the request path after the parent * * @return the content object corresponding to the path, or * null if no matching content was found * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the specified content * object wasn't readable by the user */ private Content locateContent(Request request, ContentSite site, String path) throws ContentException, ContentSecurityException { Content content = site; String name; int pos; while (content != null && path.length() > 0) { pos = path.indexOf('/'); if (pos <= 0) { name = path; } else { name = path.substring(0, pos); } content = locateChild(request, content, name); path = path.substring(name.length()); if (path.startsWith("/")) { if (isDirectory(content)) { path = path.substring(1); } } } return content; } /** * Finds the content child object corresponding to a name. This * method will first attempt a direct match. If that fails, it * will search translators and content pages linked to sections * for a matching document. Note that this method may return any * type of content object as long as it supports being presented * by the sendContent() method. * * @param request the request object * @param parent the content parent * @param name the child name * * @return the content object corresponding to the name, or * null if no matching content was found * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the specified content * object wasn't readable by the user */ private Content locateChild(Request request, Content parent, String name) throws ContentException, ContentSecurityException { User user = request.getUser(); Content content; Content[] children; // Check translator children content = request.getEnvironment().getTranslator(); if (content != null) { content = manager.getContentChild(user, content, name); if (isPage(content)) { updateRequestEnvironment(request, content); return content; } } // Check parent object children content = manager.getContentChild(user, parent, name); if (isDirectory(content) || isPage(content)) { updateRequestEnvironment(request, content); return content; } // Check parent translators children = manager.getContentChildren(user, parent, Content.TRANSLATOR_CATEGORY); for (int i = 0; i < children.length; i++) { content = locateTranslated(request, (ContentTranslator) children[i], name); if (content != null) { updateRequestEnvironment(request, content); return content; } } return null; } /** * Finds the translator child object corresponding to a name. This * will search any object linked to the translator for a matching * section or document. Note that this method may return any type * of content object as long as it supports being presented by the * sendContent() method. * * @param request the request object * @param translator the content translator * @param name the child name * * @return the content object corresponding to the name, or * null if no matching content was found * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the specified content * object wasn't readable by the user */ private Content locateTranslated(Request request, ContentTranslator translator, String name) throws ContentException, ContentSecurityException { User user = request.getUser(); ContentSection section; Content content = null; if (translator.getType() == ContentTranslator.ALIAS_TYPE) { /* TODO: enable when env.setPage() works for aliases content = translator.getAlias(user); content = manager.getContentChild(user, content, name); */ } else if (translator.getType() == ContentTranslator.REDIRECT_TYPE) { // TODO: implement this! } else if (translator.getType() == ContentTranslator.SECTION_TYPE) { section = translator.getSection(user); content = manager.getContentChild(user, translator, name); if (content == null) { content = manager.getContentChild(user, section, name); } if (content != null) { request.getEnvironment().setTranslator(translator); request.getEnvironment().setSection(section); } } return content; } /** * Finds the index page for a content parent. This method does * NOT control access permissions and should thus ONLY be used * internally in the request processing. * * @param request the request object * @param parent the content parent * * @return the index content object, or * null if no matching content was found * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the specified content * object wasn't readable by the user */ private Content locateIndexPage(Request request, Content parent) throws ContentException, ContentSecurityException { String[] index = { "index.html", "index.htm" }; Content page; for (int i = 0; i < index.length; i++) { page = locateChild(request, parent, index[i]); if (page != null) { return page; } } return null; } /** * Processes a request to a content object. * * @param request the request object * @param content the content object requested * * @throws ContentException if the database couldn't be accessed * properly * @throws ContentSecurityException if the requested content * object wasn't readable by the user * @throws TemplateException if the page template couldn't be * processed correctly * @throws RequestException if the content wasn't found */ protected void sendContent(Request request, Content content) throws ContentException, ContentSecurityException, TemplateException, RequestException { if (isDirectory(content) && !request.getPath().endsWith("/")) { request.sendRedirect(request.getPath() + "/"); } else if (content instanceof ContentSite) { sendContent(request, locateIndexPage(request, content)); } else if (content instanceof ContentFolder) { sendContent(request, locateIndexPage(request, content)); } else if (content instanceof ContentPage) { request.getEnvironment().setPage((ContentPage) content); sendContentPage(request, (ContentPage) content); } else if (content instanceof ContentFile) { request.sendFile(((ContentFile) content).getFile(), !content.hasReadAccess(null)); } else if (content instanceof ContentSection) { if (request.getEnvironment().getTranslator() != null) { content = request.getEnvironment().getTranslator(); sendContent(request, locateIndexPage(request, content)); } else { throw RequestException.RESOURCE_NOT_FOUND; } } else if (content instanceof ContentDocument) { if (request.getEnvironment().getTranslator() != null) { content = request.getEnvironment().getTranslator(); sendContent(request, locateIndexPage(request, content)); } else { sendContent(request, request.getEnvironment().getPage()); } } else { throw RequestException.RESOURCE_NOT_FOUND; } } /** * Processes a request to a content page. * * @param request the request object * @param page the page requested * * @throws TemplateException if the page template couldn't be * processed correctly */ private void sendContentPage(Request request, ContentPage page) throws TemplateException { User user = request.getUser(); Template template; template = TemplateManager.getPageTemplate(user, page); template.processNormal(request, getContentManager()); } /** * Processes a request to a template file. The template file name * is relative to the web context directory, and the output MIME * type will always be set to "text/html". * * @param request the request object * @param templateName the template file name * * @throws TemplateException if the file template couldn't be * processed correctly */ protected void sendTemplate(Request request, String templateName) throws TemplateException { Template template; template = TemplateManager.getFileTemplate(templateName); template.processNormal(request, getContentManager()); } /** * Processes an error response. The default error template file * will be used and the response headers will be set with the * specified error code. * * @param request the request object * @param code the response HTTP error code * * @throws TemplateException if the file template couldn't be * processed correctly */ protected void sendError(Request request, int code) throws TemplateException { Template template; template = TemplateManager.getFileTemplate("error.ftl"); template.processError(request, getContentManager(), code); } /** * Checks if the specified content object represents a directory. * * @param content the content object to check * * @return true if the content object is a directory, or * false otherwise */ private boolean isDirectory(Content content) { return content instanceof ContentSite || content instanceof ContentFolder || content instanceof ContentSection || content instanceof ContentDocument || content instanceof ContentForum || content instanceof ContentTopic; } /** * Checks if the specified content object represents a page. * * @param content the content object to check * * @return true if the content object is a page, or * false otherwise */ private boolean isPage(Content content) { return content instanceof ContentPage || content instanceof ContentFile; } /** * Updates the request environment with the specified content * object. * * @param request the request * @param content the content object */ private void updateRequestEnvironment(Request request, Content content) { if (content instanceof ContentPage) { request.getEnvironment().setPage((ContentPage) content); } else if (content instanceof ContentSection) { request.getEnvironment().setSection((ContentSection) content); } else if (content instanceof ContentDocument) { request.getEnvironment().setDocument((ContentDocument) content); } else if (content instanceof ContentForum) { request.getEnvironment().setForum((ContentForum) content); } else if (content instanceof ContentTopic) { request.getEnvironment().setTopic((ContentTopic) content); } } }