/* This file is part of leafdigital picstory. picstory 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 3 of the License, or (at your option) any later version. picstory 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 picstory. If not, see <http://www.gnu.org/licenses/>. Copyright 2010 Samuel Marshall. */ package com.leafdigital.picstory; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.regex.*; import javax.servlet.ServletException; import javax.servlet.http.*; /** * Servlet handles all requests, dispatching them to other classes. */ public class MainServlet extends HttpServlet { /** * Hash in URL (8 characters). */ public final static String REGEX_PART_HASH = "[a-z0-9]{8}"; /** * Name containing lower-case letters, digits, _ and - */ public final static String REGEX_PART_LCNAME = "[a-z0-9_-]+"; /** * Name containing lower-case and upper-case letters, digits, _ and - */ public final static String REGEX_PART_NAME = "[a-zA-Z0-9_-]+"; private final static Pattern REGEX_RESOURCE = Pattern.compile( "/R/(" + REGEX_PART_LCNAME + ")\\.(" + REGEX_PART_HASH + ")(\\." + REGEX_PART_LCNAME + ")"); private final static Pattern REGEX_STORY = Pattern.compile( "/(" + REGEX_PART_LCNAME + ")/"); private final static Pattern REGEX_STORY_NO_SLASH = Pattern.compile( "/(" + REGEX_PART_LCNAME + ")"); private final static Pattern REGEX_PIC = Pattern.compile( "/(" + REGEX_PART_LCNAME + ")/(" + REGEX_PART_NAME + ")\\.(" + REGEX_PART_HASH + ")\\.(" + REGEX_PART_LCNAME + ")\\.jpg"); private final static Pattern REGEX_STORY_BASIC_XML = Pattern.compile( "/(" + REGEX_PART_LCNAME + ")/basicxml"); private ResourceHandler resource; private StoryHandler story; private IndexHandler index; private TemplateManager templates; private StoryCache stories; private String siteName, indexIntroXhtml, indexFinalXhtml, storyFinalXhtml; @Override public void init() throws ServletException { System.setProperty("java.awt.headless", "true"); super.init(); resource = new ResourceHandler(this, getFolderParameter("resource-folder")); File cacheRoot = getFolderParameter("cache-folder"); File storyRoot = getFolderParameter("story-folder"); try { stories = new StoryCache(this, cacheRoot, storyRoot); index = new IndexHandler(this, cacheRoot, storyRoot); } catch(InternalException e) { throw new ServletException(e); } story = new StoryHandler(this, cacheRoot, storyRoot, Integer.parseInt(getParameter("max-resize-threads"))); templates = new TemplateManager( getFolderParameter("template-folder"), resource); siteName = getParameter("site-name"); indexIntroXhtml = getParameter("index-intro"); indexFinalXhtml = getParameter("index-final"); storyFinalXhtml = getParameter("story-final"); } /** * Gets parameter. * @param name Parameter name * @return Parameter value * @throws ServletException If not found */ private String getParameter(String name) throws ServletException { String param = getServletConfig().getInitParameter(name); if(param == null) { throw new ServletException("Required parameter '" + name + "' missing"); } return param; } private File getFolderParameter(String name) throws ServletException { String param = getParameter(name); File result; if(param.startsWith("/")) { result = new File(param); } else { result = new File(getServletContext().getRealPath("/" + param)); } if(!result.exists()) { throw new ServletException("Parameter '" + name + "': Path " + result + " not found"); } return result; } /** * @return Template manager */ public TemplateManager getTemplates() { return templates; } /** * @return Story cache */ public StoryCache getStories() { return stories; } /** * @return Site name */ public String getSiteName() { return siteName; } /** * @return XHTML text that goes at the top of the index page */ public String getIndexIntroXhtml() { return indexIntroXhtml; } /** * @return XHTML text that goes at the bottom of the index page */ public String getIndexFinalXhtml() { return indexFinalXhtml; } /** * @return XHTML text that goes at the bottom of each story page */ public String getStoryFinalXhtml() { return storyFinalXhtml; } /** * Sends a page using the main page template. * @param r Request * @param statusCode Status code (HttpServletResponse.SC_xx) * @param className Class name of page body tag * @param title Title (null = only site title); should not be escaped yet * @param content Main content of page (to be placed inside page template) * @throws IOException Any error * @throws InternalException Unexpected errors */ public void sendPage(Request r, int statusCode, String className, String title, String content) throws IOException, InternalException { if(title == null) { title = siteName; } else { title = siteName + " - " + Util.esc(title); } Template pageTemplate = templates.get(TemplateManager.Name.PAGE); r.outputXhtml(statusCode, pageTemplate.getString( r.getPathToRoot(), new String[] { "CLASS", "TITLE", "MAIN" }, new String[] { className, title, content })); } /** * Sends a page using the main page template, with status 200 OK. * @param r Request * @param className Class name of page body tag * @param title Title (null = only site title) * @param content Main content of page (to be placed inside page template) * @throws IOException Any error * @throws InternalException Unexpected errors */ public void sendPage(Request r, String className, String title, String content) throws IOException, InternalException { sendPage(r, HttpServletResponse.SC_OK, className, title, content); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Request r = new Request(request, response); try { try { // Refresh templates if required if(r.isReload()) { templates.reload(); resource.reload(); } // Annoyingly, path info is "/" even if you request the page without a / // on the end; handle both normal behaviour and this weird behaviour String path = request.getPathInfo(); if(path == null || path.equals("") || (path.equals("/") && !request.getRequestURI().endsWith("/"))) { r.redirect(request.getRequestURI() + "/"); return; } if(path.equals("/")) { index.get(r); } Matcher m = REGEX_RESOURCE.matcher(path); if(m.matches()) { resource.get(r, m.group(1), m.group(2), m.group(3)); return; } m = REGEX_STORY_BASIC_XML.matcher(path); if(m.matches()) { story.getBasicXml(r, m.group(1)); return; } m = REGEX_STORY_NO_SLASH.matcher(path); if(m.matches()) { r.redirect(m.group(1) + "/"); return; } m = REGEX_STORY.matcher(path); if(m.matches()) { story.get(r, m.group(1)); return; } m = REGEX_PIC.matcher(path); if(m.matches()) { story.getPic(r, m.group(1), m.group(2), m.group(3), m.group(4)); return; } throw new UserException(HttpServletResponse.SC_NOT_FOUND, "Path '" + path + "' not found"); } catch(UserException e) { throw e; } catch(Throwable t) { throw new InternalException(t); } } catch(UserException e) { if(e instanceof InternalException) { System.err.println( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " ** picstory exception"); e.printStackTrace(); System.err.println(); } try { e.display(r, this); } catch(InternalException e2) { throw new ServletException(e2); } } } }