/** * Copyright (c) 2003-2005 , David A. Czarnecki * All rights reserved. * * Portions Copyright (c) 2003-2005 by Mark Lussier * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the "David A. Czarnecki" and "blojsom" nor the names of * its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Products derived from this software may not be called "blojsom", * nor may "blojsom" appear in their name, without prior written permission of * David A. Czarnecki. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.blojsom.servlet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.blojsom.BlojsomException; import org.blojsom.blog.*; import org.blojsom.dispatcher.BlojsomDispatcher; import org.blojsom.fetcher.BlojsomFetcher; import org.blojsom.fetcher.BlojsomFetcherException; import org.blojsom.plugin.BlojsomPlugin; import org.blojsom.plugin.BlojsomPluginException; import org.blojsom.util.BlojsomUtils; import org.blojsom.util.resources.ResourceManager; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.*; /** * BlojsomServlet * * @author David Czarnecki * @author Mark Lussier * @version $Id: BlojsomServlet.java,v 1.5.2.1 2005/07/21 14:11:04 johnan Exp $ */ public class BlojsomServlet extends BlojsomBaseServlet { // BlojsomServlet initialization properties from web.xml private static final String BLOJSOM_DISPATCHER_MAP_CONFIGURATION_IP = "dispatcher-configuration"; private Log _logger = LogFactory.getLog(BlojsomServlet.class); private Map _plugins; private Map _dispatchers; private ResourceManager _resourceManager; /** * Create a new blojsom servlet instance */ public BlojsomServlet() { } /** * Configure the dispatchers that blojsom will use when passing a request/response on to a * particular template * * @param servletConfig Servlet configuration information */ protected void configureDispatchers(ServletConfig servletConfig) throws ServletException { String templateConfiguration = servletConfig.getInitParameter(BLOJSOM_DISPATCHER_MAP_CONFIGURATION_IP); _dispatchers = new HashMap(); Properties templateMapProperties = new Properties(); InputStream is = servletConfig.getServletContext().getResourceAsStream(templateConfiguration); try { templateMapProperties.load(is); is.close(); Iterator templateIterator = templateMapProperties.keySet().iterator(); while (templateIterator.hasNext()) { String templateExtension = (String) templateIterator.next(); String templateDispatcherClass = templateMapProperties.getProperty(templateExtension); Class dispatcherClass = Class.forName(templateDispatcherClass); BlojsomDispatcher dispatcher = (BlojsomDispatcher) dispatcherClass.newInstance(); dispatcher.init(servletConfig, _blojsomConfiguration); _dispatchers.put(templateExtension, dispatcher); _logger.debug("Added template dispatcher: " + templateDispatcherClass); } } catch (InstantiationException e) { _logger.error(e); } catch (IllegalAccessException e) { _logger.error(e); } catch (ClassNotFoundException e) { _logger.error(e); } catch (IOException e) { _logger.error(e); throw new ServletException(e); } catch (BlojsomException e) { _logger.error(e); throw new ServletException(e); } } /** * Configure the flavors for the blog which map flavor values like "html" and "rss" to * the proper template and content type * * @param servletConfig Servlet configuration information * @since blojsom 2.24 */ protected void configureFlavorsForBlog(ServletConfig servletConfig, BlogUser blogUser) throws ServletException { String flavorConfiguration = servletConfig.getInitParameter(BLOJSOM_FLAVOR_CONFIGURATION_IP); if (BlojsomUtils.checkNullOrBlank(flavorConfiguration)) { flavorConfiguration = DEFAULT_FLAVOR_CONFIGURATION_FILE; } Map flavors = new HashMap(); Map flavorToTemplateMap = new HashMap(); Map flavorToContentTypeMap = new HashMap(); String user = blogUser.getId(); Properties flavorProperties = new Properties(); InputStream is = servletConfig.getServletContext().getResourceAsStream(_baseConfigurationDirectory + user + '/' + flavorConfiguration); try { flavorProperties.load(is); is.close(); _logger.debug("Loaded flavor information for user: " + user); Iterator flavorIterator = flavorProperties.keySet().iterator(); while (flavorIterator.hasNext()) { String flavor = (String) flavorIterator.next(); String[] flavorMapping = BlojsomUtils.parseCommaList(flavorProperties.getProperty(flavor)); flavors.put(flavor, flavor); flavorToTemplateMap.put(flavor, flavorMapping[0]); flavorToContentTypeMap.put(flavor, flavorMapping[1]); } blogUser.setFlavors(flavors); blogUser.setFlavorToTemplate(flavorToTemplateMap); blogUser.setFlavorToContentType(flavorToContentTypeMap); } catch (IOException e) { _logger.error(e); throw new ServletException(e); } } /** * Load the plugins * * @param servletConfig {@link ServletConfig} * @throws ServletException If there is an error loading the plugin configuration file * @since blojsom 2.24 */ protected void configurePlugins(ServletConfig servletConfig) throws ServletException { // Instantiate the plugins Properties pluginProperties; String pluginConfiguration = servletConfig.getInitParameter(BLOJSOM_PLUGIN_CONFIGURATION_IP); if (BlojsomUtils.checkNullOrBlank(pluginConfiguration)) { _logger.error("No plugin configuration file specified"); throw new ServletException("No plugin configuration file specified"); } Iterator pluginIterator; _plugins = new HashMap(); String pluginConfigurationLocation = _baseConfigurationDirectory + pluginConfiguration; pluginProperties = new Properties(); try { pluginProperties = BlojsomUtils.loadProperties(servletConfig, pluginConfigurationLocation); } catch (BlojsomException e) { _logger.error(e); throw new ServletException(e); } // Instantiate the plugin classes pluginIterator = pluginProperties.keySet().iterator(); while (pluginIterator.hasNext()) { String plugin = (String) pluginIterator.next(); if (plugin.indexOf(BLOJSOM_PLUGIN_CHAIN) != -1) { // Just in case we are migrating from a blojsom 1.x installation _logger.debug("Skipping blojsom plugin chain in global plugin configuration file"); } else { String pluginClassName = pluginProperties.getProperty(plugin); try { Class pluginClass = Class.forName(pluginClassName); BlojsomPlugin blojsomPlugin = (BlojsomPlugin) pluginClass.newInstance(); blojsomPlugin.init(servletConfig, _blojsomConfiguration); _plugins.put(plugin, blojsomPlugin); _logger.info("Added blojsom plugin: " + pluginClassName); } catch (BlojsomPluginException e) { _logger.error(e); } catch (InstantiationException e) { _logger.error(e); } catch (IllegalAccessException e) { _logger.error(e); } catch (ClassNotFoundException e) { _logger.error(e); } } } } /** * Configure the plugins that blojsom will use for a given blog * * @param servletConfig Servlet configuration information * @param blogUser {@link BlogUser} information * @since blojsom 2.24 */ protected void configurePluginsForBlog(ServletConfig servletConfig, BlogUser blogUser) throws ServletException { Properties pluginProperties; String pluginConfiguration = servletConfig.getInitParameter(BLOJSOM_PLUGIN_CONFIGURATION_IP); Iterator pluginIterator = _plugins.keySet().iterator(); if (BlojsomUtils.checkNullOrBlank(pluginConfiguration)) { _logger.error("No plugin configuration file specified"); throw new ServletException("No plugin configuration file specified"); } Map pluginChainMap = new HashMap(); String user = blogUser.getId(); InputStream is = servletConfig.getServletContext().getResourceAsStream(_baseConfigurationDirectory + user + '/' + pluginConfiguration); pluginProperties = new Properties(); try { pluginProperties.load(is); is.close(); pluginIterator = pluginProperties.keySet().iterator(); while (pluginIterator.hasNext()) { String plugin = (String) pluginIterator.next(); if (plugin.indexOf(BLOJSOM_PLUGIN_CHAIN) != -1) { pluginChainMap.put(plugin, BlojsomUtils.parseCommaList(pluginProperties.getProperty(plugin))); _logger.debug("Added plugin chain: " + plugin + '=' + pluginProperties.getProperty(plugin) + " for user: " + user); } } blogUser.setPluginChain(pluginChainMap); } catch (IOException e) { _logger.error(e); throw new ServletException(e); } } /** * Instantiate the resource manager * * @throws ServletException If there is an error instantiating the resource manager class */ protected void configureResourceManager() throws ServletException { String resourceManagerClass = _blojsomConfiguration.getResourceManager(); try { Class resourceManagerClazz = Class.forName(resourceManagerClass); _resourceManager = (ResourceManager) resourceManagerClazz.newInstance(); _resourceManager.init(_blojsomConfiguration); } catch (InstantiationException e) { _logger.error(e); throw new ServletException(e); } catch (IllegalAccessException e) { _logger.error(e); throw new ServletException(e); } catch (ClassNotFoundException e) { _logger.error(e); throw new ServletException(e); } catch (BlojsomException e) { _logger.error(e); throw new ServletException(e); } } /** * Initialize blojsom: configure blog, configure flavors, configure dispatchers * * @param servletConfig Servlet configuration information * @throws ServletException If there is an error initializing blojsom */ public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); configureBlojsom(servletConfig); configureDispatchers(servletConfig); configurePlugins(servletConfig); configureResourceManager(); _logger.debug("blojsom: All Your Blog Are Belong To Us"); } /** * Service a request to blojsom * * @param httpServletRequest Request * @param httpServletResponse Response * @throws ServletException If there is an error processing the request * @throws IOException If there is an error in IO */ protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException { try { httpServletRequest.setCharacterEncoding(UTF8); } catch (UnsupportedEncodingException e) { _logger.error(e); } // Make sure that we have a request URI ending with a / otherwise we need to // redirect so that the browser can handle relative link generation if (!httpServletRequest.getRequestURI().endsWith("/")) { StringBuffer redirectURL = new StringBuffer(); redirectURL.append(httpServletRequest.getRequestURI()); redirectURL.append("/"); if (httpServletRequest.getParameterMap().size() > 0) { redirectURL.append("?"); redirectURL.append(BlojsomUtils.convertRequestParams(httpServletRequest)); } _logger.debug("Redirecting the user to: " + redirectURL.toString()); httpServletResponse.sendRedirect(redirectURL.toString()); return; } // Check for an overriding id String user = httpServletRequest.getParameter("id"); if (BlojsomUtils.checkNullOrBlank(user)) { String userFromPath = BlojsomUtils.getUserFromPath(httpServletRequest.getPathInfo()); if (userFromPath == null) { user = _blojsomConfiguration.getDefaultUser(); } else { user = userFromPath; } } // Load the blog information BlogUser blogUser = null; try { blogUser = _blojsomConfiguration.loadBlog(user); } catch (BlojsomException e) { // Fallback to current default-blog-user functionality if (!BlojsomUtils.checkNullOrBlank(_blojsomConfiguration.getDefaultUser())) { try { blogUser = _blojsomConfiguration.loadBlog(_blojsomConfiguration.getDefaultUser()); } catch (BlojsomException e2) { _logger.error(e2); httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "Requested blog not found: " + user); return; } } } // If the blog ID isn't in the known list of blog IDs, add it if (!_blojsomConfiguration.checkBlogIDExists(blogUser.getId())) { _blojsomConfiguration.addBlogID(blogUser.getId()); } // If blog ID is not in the list of known blog IDs, add it so that plugins and other components know it exists if (!_blojsomConfiguration.checkBlogIDExists(blogUser.getId())) { _blojsomConfiguration.addBlogID(blogUser.getId()); } configureFlavorsForBlog(_servletConfig, blogUser); configurePluginsForBlog(_servletConfig, blogUser); Blog blog = blogUser.getBlog(); // Check to see if we need to dynamically determine blog-base-url and blog-url? BlojsomUtils.resolveDynamicBaseAndBlogURL(httpServletRequest, blog, user); // Determine the requested flavor String flavor = httpServletRequest.getParameter(FLAVOR_PARAM); if (BlojsomUtils.checkNullOrBlank(flavor)) { flavor = blog.getBlogDefaultFlavor(); if (blogUser.getFlavors().get(flavor) == null) { flavor = DEFAULT_FLAVOR_HTML; } } else { if (blogUser.getFlavors().get(flavor) == null) { flavor = blog.getBlogDefaultFlavor(); if (blogUser.getFlavors().get(flavor) == null) { flavor = DEFAULT_FLAVOR_HTML; } } } // Setup the initial context for the fetcher, plugins, and finally the dispatcher HashMap context = new HashMap(); // Setup the resource manager in the context context.put(BLOJSOM_RESOURCE_MANAGER_CONTEXT_KEY, _resourceManager); context.put(BLOJSOM_REQUESTED_FLAVOR, flavor); BlogEntry[] entries = null; BlogCategory[] categories = null; // Fetch the categories and entries for the request try { categories = _fetcher.fetchCategories(httpServletRequest, httpServletResponse, blogUser, flavor, context); entries = _fetcher.fetchEntries(httpServletRequest, httpServletResponse, blogUser, flavor, context); } catch (BlojsomFetcherException e) { _logger.error(e); } String[] pluginChain = null; Map pluginChainMap = blogUser.getPluginChain(); // Check to see if the user would like to override the plugin chain if (httpServletRequest.getParameter(PLUGINS_PARAM) != null) { pluginChain = BlojsomUtils.parseCommaList(httpServletRequest.getParameter(PLUGINS_PARAM)); } else { String pluginChainMapKey = flavor + '.' + BLOJSOM_PLUGIN_CHAIN; String[] pluginChainValue = (String[]) pluginChainMap.get(pluginChainMapKey); if (pluginChainValue != null && pluginChainValue.length > 0) { pluginChain = (String[]) pluginChainMap.get(pluginChainMapKey); } else { pluginChain = (String[]) pluginChainMap.get(BLOJSOM_PLUGIN_CHAIN); } } // Invoke the plugins in the order in which they were specified if ((entries != null) && (pluginChain != null) && (!"".equals(pluginChain))) { for (int i = 0; i < pluginChain.length; i++) { String plugin = pluginChain[i]; if (_plugins.containsKey(plugin)) { BlojsomPlugin blojsomPlugin = (BlojsomPlugin) _plugins.get(plugin); _logger.debug("blojsom plugin execution: " + blojsomPlugin.getClass().getName()); try { entries = blojsomPlugin.process(httpServletRequest, httpServletResponse, blogUser, context, entries); blojsomPlugin.cleanup(); } catch (BlojsomPluginException e) { _logger.error(e); } } else { _logger.error("No plugin loaded for: " + plugin); } } } String blogdate = null; String blogISO8601Date = null; String blogUTCDate = null; Date blogDateObject = null; boolean sendLastModified = true; if (httpServletRequest.getParameter(OVERRIDE_LASTMODIFIED_PARAM) != null) { sendLastModified = Boolean.getBoolean(httpServletRequest.getParameter(OVERRIDE_LASTMODIFIED_PARAM)); } // If we have entries, construct a last modified on the most recent entry // Additionally, set the blog date if (sendLastModified) { if ((entries != null) && (entries.length > 0)) { BlogEntry _entry = entries[0]; long _lastmodified; if (_entry.getNumComments() > 0) { BlogComment _comment = _entry.getCommentsAsArray()[_entry.getNumComments() - 1]; _lastmodified = _comment.getCommentDateLong(); _logger.debug("Adding last-modified header for most recent entry comment"); } else { _lastmodified = _entry.getLastModified(); _logger.debug("Adding last-modified header for most recent blog entry"); } // Check for the Last-Modified object from one of the plugins if (context.containsKey(BLOJSOM_LAST_MODIFIED)) { Long lastModified = (Long) context.get(BLOJSOM_LAST_MODIFIED); if (lastModified.longValue() > _lastmodified) { _lastmodified = lastModified.longValue(); } } // Generates an ETag header based on the string value of LastModified as an ISO8601 Format String etagLastModified = BlojsomUtils.getISO8601Date(new Date(_lastmodified)); httpServletResponse.addHeader(HTTP_ETAG, "\"" + BlojsomUtils.digestString(etagLastModified) + "\""); httpServletResponse.addDateHeader(HTTP_LASTMODIFIED, _lastmodified); blogdate = entries[0].getRFC822Date(); blogISO8601Date = entries[0].getISO8601Date(); blogDateObject = entries[0].getDate(); blogUTCDate = BlojsomUtils.getUTCDate(entries[0].getDate()); } else { _logger.debug("Adding last-modified header for current date"); Date today = new Date(); blogdate = BlojsomUtils.getRFC822Date(today); blogISO8601Date = BlojsomUtils.getISO8601Date(today); blogUTCDate = BlojsomUtils.getUTCDate(today); blogDateObject = today; httpServletResponse.addDateHeader(HTTP_LASTMODIFIED, today.getTime()); // Generates an ETag header based on the string value of LastModified as an ISO8601 Format httpServletResponse.addHeader(HTTP_ETAG, "\"" + BlojsomUtils.digestString(blogISO8601Date) + "\""); } } context.put(BLOJSOM_DATE, blogdate); context.put(BLOJSOM_DATE_ISO8601, blogISO8601Date); context.put(BLOJSOM_DATE_OBJECT, blogDateObject); context.put(BLOJSOM_DATE_UTC, blogUTCDate); // Finish setting up the context for the dispatcher context.put(BLOJSOM_BLOG, blog); context.put(BLOJSOM_SITE_URL, blog.getBlogBaseURL()); context.put(BLOJSOM_ENTRIES, entries); context.put(BLOJSOM_CATEGORIES, categories); context.put(BLOJSOM_COMMENTS_ENABLED, blog.getBlogCommentsEnabled()); context.put(BLOJSOM_VERSION, BLOJSOM_VERSION_NUMBER); context.put(BLOJSOM_USER, blogUser.getId()); context.put(BLOJSOM_PLUGINS, Collections.unmodifiableMap(_plugins)); // Forward the request on to the template for the requested flavor String flavorTemplate; Map flavorToTemplate = blogUser.getFlavorToTemplate(); if (flavorToTemplate.get(flavor) == null) { flavorTemplate = (String) flavorToTemplate.get(DEFAULT_FLAVOR_HTML); } else { flavorTemplate = (String) flavorToTemplate.get(flavor); } // Get the content type for the requested flavor Map flavorToContentType = blogUser.getFlavorToContentType(); String flavorContentType = (String) flavorToContentType.get(flavor); String templateExtension = BlojsomUtils.getFileExtension(flavorTemplate); _logger.debug("Template extension: " + templateExtension); // Retrieve the appropriate dispatcher for the template BlojsomDispatcher dispatcher = (BlojsomDispatcher) _dispatchers.get(templateExtension); dispatcher.dispatch(httpServletRequest, httpServletResponse, blogUser, context, flavorTemplate, flavorContentType); } /** * Called when removing the servlet from the servlet container. Also calls the * {@link BlojsomPlugin#destroy} method for each of the plugins loaded by * blojsom. This method also calls the {@link BlojsomFetcher#destroy} method for the fetcher loaded * by blojsom. */ public void destroy() { super.destroy(); // Destroy the fetcher try { _fetcher.destroy(); } catch (BlojsomFetcherException e) { _logger.error(e); } // Destroy the plugins Iterator pluginIteratorIterator = _plugins.keySet().iterator(); while (pluginIteratorIterator.hasNext()) { String pluginName = (String) pluginIteratorIterator.next(); BlojsomPlugin plugin = (BlojsomPlugin) _plugins.get(pluginName); try { plugin.destroy(); _logger.debug("Removed blojsom plugin: " + plugin.getClass().getName()); } catch (BlojsomPluginException e) { _logger.error(e); } } _logger.debug("blojsom destroyed"); } }