001: /**
002: * Copyright (c) 2003-2007, David A. Czarnecki
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * Redistributions of source code must retain the above copyright notice, this list of conditions and the
009: * following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
011: * following disclaimer in the documentation and/or other materials provided with the distribution.
012: * Neither the name of "David A. Czarnecki" and "blojsom" nor the names of its contributors may be used to
013: * endorse or promote products derived from this software without specific prior written permission.
014: * Products derived from this software may not be called "blojsom", nor may "blojsom" appear in their name,
015: * without prior written permission of David A. Czarnecki.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
018: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
019: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
021: * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
022: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
025: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026: * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
029: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030: */package org.blojsom.servlet;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.blojsom.blog.Blog;
035: import org.blojsom.blog.Category;
036: import org.blojsom.blog.Comment;
037: import org.blojsom.blog.Entry;
038: import org.blojsom.dispatcher.Dispatcher;
039: import org.blojsom.fetcher.Fetcher;
040: import org.blojsom.fetcher.FetcherException;
041: import org.blojsom.plugin.Plugin;
042: import org.blojsom.plugin.PluginException;
043: import org.blojsom.util.BlojsomConstants;
044: import org.blojsom.util.BlojsomUtils;
045: import org.springframework.beans.BeansException;
046: import org.springframework.context.support.ClassPathXmlApplicationContext;
047:
048: import javax.servlet.ServletConfig;
049: import javax.servlet.ServletException;
050: import javax.servlet.http.HttpServlet;
051: import javax.servlet.http.HttpServletRequest;
052: import javax.servlet.http.HttpServletResponse;
053: import java.io.IOException;
054: import java.io.UnsupportedEncodingException;
055: import java.util.Date;
056: import java.util.HashMap;
057: import java.util.Map;
058: import java.util.Properties;
059:
060: /**
061: * BlojsomServlet
062: *
063: * @author David Czarnecki
064: * @version $Id: BlojsomServlet.java,v 1.12 2007/01/17 02:35:18 czarneckid Exp $
065: * @since blojsom 3.0
066: */
067: public class BlojsomServlet extends HttpServlet {
068:
069: protected Log _logger = LogFactory.getLog(BlojsomServlet.class);
070:
071: protected String[] BLOJSOM_CONFIGURATION_FILES = { "blojsom.xml" };
072: protected ClassPathXmlApplicationContext _classPathXmlApplicationContext;
073:
074: /**
075: * Initialize blojsom
076: *
077: * @param servletConfig {@link ServletConfig}
078: * @throws ServletException If there is an error initializing blojsom
079: */
080: public void init(ServletConfig servletConfig)
081: throws ServletException {
082: super .init(servletConfig);
083:
084: ServletConfigFactoryBean.setServletConfig(servletConfig);
085:
086: try {
087: _classPathXmlApplicationContext = new ClassPathXmlApplicationContext(
088: BLOJSOM_CONFIGURATION_FILES);
089: } catch (BeansException e) {
090: if (_logger.isErrorEnabled()) {
091: _logger.error(e);
092: }
093:
094: throw new ServletException(e);
095: }
096:
097: servletConfig.getServletContext().setAttribute(
098: BlojsomConstants.BLOJSOM_APPLICATION_CONTEXT,
099: _classPathXmlApplicationContext);
100:
101: if (_logger.isDebugEnabled()) {
102: _logger.debug("blojsom: All Your Blog Are Belong To Us");
103: }
104: }
105:
106: /**
107: * Handle requests made to blojsom
108: *
109: * @param httpServletRequest {@link HttpServletRequest} request
110: * @param httpServletResponse {@link HttpServletResponse} response
111: * @throws ServletException If there is an error serving the request
112: * @throws IOException If there is an error serving the request
113: */
114: protected void service(HttpServletRequest httpServletRequest,
115: HttpServletResponse httpServletResponse)
116: throws ServletException, IOException {
117: try {
118: httpServletRequest
119: .setCharacterEncoding(BlojsomConstants.UTF8);
120: } catch (UnsupportedEncodingException e) {
121: if (_logger.isErrorEnabled()) {
122: _logger.error(e);
123: }
124: }
125:
126: // Make sure that we have a request URI ending with a / otherwise we need to
127: // redirect so that the browser can handle relative link generation
128: if (!httpServletRequest.getRequestURI().endsWith("/")) {
129: StringBuffer redirectURL = new StringBuffer();
130: redirectURL.append(httpServletRequest.getRequestURI());
131: redirectURL.append("/");
132: if (httpServletRequest.getParameterMap().size() > 0) {
133: redirectURL.append("?");
134: redirectURL.append(BlojsomUtils
135: .convertRequestParams(httpServletRequest));
136: }
137:
138: if (_logger.isDebugEnabled()) {
139: _logger.debug("Redirecting the user to: "
140: + redirectURL.toString());
141: }
142:
143: httpServletResponse.sendRedirect(redirectURL.toString());
144:
145: return;
146: }
147:
148: Properties blojsomDefaultProperties = (Properties) _classPathXmlApplicationContext
149: .getBean("defaultProperties");
150:
151: // Check for an overriding id
152: String blogId = httpServletRequest
153: .getParameter(BlojsomConstants.BLOG_ID_PARAM);
154: if (BlojsomUtils.checkNullOrBlank(blogId)) {
155: String blogIdFromPath = BlojsomUtils
156: .getBlogFromPath(httpServletRequest.getPathInfo());
157: if (blogIdFromPath == null) {
158: blogId = blojsomDefaultProperties
159: .getProperty(BlojsomConstants.DEFAULT_BLOG_IP);
160: } else {
161: blogId = blogIdFromPath;
162: }
163: }
164:
165: if (BlojsomUtils.checkNullOrBlank(blogId)) {
166: httpServletResponse.sendError(
167: HttpServletResponse.SC_NOT_FOUND,
168: "Blog ID not specified");
169:
170: return;
171: }
172:
173: Fetcher fetcher = (Fetcher) _classPathXmlApplicationContext
174: .getBean("fetcher");
175:
176: Blog blog;
177: try {
178: blog = fetcher.loadBlog(blogId);
179: } catch (FetcherException e) {
180: if (_logger.isErrorEnabled()) {
181: _logger.error(e);
182: }
183:
184: // Try and use the default blog ID if a blog ID is specified and unable to load the blog
185: String defaultBlogId = blojsomDefaultProperties
186: .getProperty(BlojsomConstants.DEFAULT_BLOG_IP);
187: if (BlojsomUtils.checkNullOrBlank(defaultBlogId)) {
188: httpServletResponse.sendError(
189: HttpServletResponse.SC_NOT_FOUND,
190: "Unable to load blog ID: " + blogId);
191:
192: return;
193: } else {
194: try {
195: blog = fetcher.loadBlog(defaultBlogId);
196: } catch (FetcherException e1) {
197: if (_logger.isErrorEnabled()) {
198: _logger.error(e);
199: }
200:
201: httpServletResponse.sendError(
202: HttpServletResponse.SC_NOT_FOUND,
203: "Unable to load blog ID: " + defaultBlogId);
204:
205: return;
206: }
207: }
208: }
209:
210: if ("true".equals(blog
211: .getProperty(BlojsomConstants.USE_DYNAMIC_BLOG_URLS))) {
212: BlojsomUtils.resolveDynamicBaseAndBlogURL(
213: httpServletRequest, blog, blogId);
214: }
215:
216: // Determine the requested flavor
217: String flavor = httpServletRequest
218: .getParameter(BlojsomConstants.FLAVOR_PARAM);
219: if (BlojsomUtils.checkNullOrBlank(flavor)) {
220: flavor = blog
221: .getProperty(BlojsomConstants.BLOG_DEFAULT_FLAVOR_IP);
222: if (blog.getTemplates().get(flavor) == null) {
223: flavor = BlojsomConstants.DEFAULT_FLAVOR_HTML;
224: }
225: } else {
226: if (blog.getTemplates().get(flavor) == null) {
227: flavor = blog
228: .getProperty(BlojsomConstants.BLOG_DEFAULT_FLAVOR_IP);
229: if (blog.getTemplates().get(flavor) == null) {
230: flavor = BlojsomConstants.DEFAULT_FLAVOR_HTML;
231: }
232: }
233: }
234:
235: // Setup the initial context for the fetcher, plugins, and finally the dispatcher
236: HashMap context = new HashMap();
237:
238: // Setup the resource manager in the context
239: context.put(BlojsomConstants.RESOURCE_MANAGER_CONTEXT_KEY,
240: _classPathXmlApplicationContext
241: .getBean("resourceManager"));
242: context.put(BlojsomConstants.BLOJSOM_REQUESTED_FLAVOR, flavor);
243:
244: Entry[] entries = null;
245: Category[] categories = null;
246:
247: try {
248: categories = fetcher.fetchCategories(httpServletRequest,
249: httpServletResponse, blog, flavor, context);
250: entries = fetcher.fetchEntries(httpServletRequest,
251: httpServletResponse, blog, flavor, context);
252: } catch (FetcherException e) {
253: if (_logger.isErrorEnabled()) {
254: _logger.error(e);
255: }
256: }
257:
258: String[] pluginChain;
259: Map plugins = blog.getPlugins();
260:
261: // Check to see if the user would like to override the plugin chain
262: if (httpServletRequest
263: .getParameter(BlojsomConstants.PLUGINS_PARAM) != null) {
264: pluginChain = BlojsomUtils
265: .parseCommaList(httpServletRequest
266: .getParameter(BlojsomConstants.PLUGINS_PARAM));
267: } else {
268: if (plugins.containsKey(flavor)
269: && !BlojsomUtils.checkNullOrBlank(((String) plugins
270: .get(flavor)).trim())) {
271: pluginChain = BlojsomUtils.parseOnlyCommaList(
272: (String) plugins.get(flavor), true);
273: } else {
274: pluginChain = BlojsomUtils.parseOnlyCommaList(
275: (String) plugins.get("default"), true);
276: }
277: }
278:
279: // Invoke the plugins in the order in which they were specified
280: if ((entries != null) && (pluginChain != null)
281: && (pluginChain.length > 0)) {
282: for (int i = 0; i < pluginChain.length; i++) {
283: String plugin = pluginChain[i];
284: try {
285: Plugin pluginToExecute = (Plugin) _classPathXmlApplicationContext
286: .getBean(plugin);
287: if (_logger.isDebugEnabled()) {
288: _logger.debug("blojsom plugin execution: "
289: + pluginToExecute.getClass().getName());
290: }
291: try {
292: entries = pluginToExecute.process(
293: httpServletRequest,
294: httpServletResponse, blog, context,
295: entries);
296: pluginToExecute.cleanup();
297: } catch (PluginException e) {
298: if (_logger.isErrorEnabled()) {
299: _logger.error(e);
300: }
301: }
302: } catch (BeansException e) {
303: if (_logger.isErrorEnabled()) {
304: _logger
305: .error("Plugin not available: "
306: + plugin);
307: }
308: }
309: }
310: } else {
311: if (_logger.isDebugEnabled()) {
312: _logger
313: .debug("No entries available for plugins to process or no plugins specified for flavor");
314: }
315: }
316:
317: String blogdate = null;
318: String blogISO8601Date = null;
319: String blogUTCDate = null;
320: Date blogDateObject = null;
321:
322: boolean sendLastModified = true;
323: if (httpServletRequest
324: .getParameter(BlojsomConstants.OVERRIDE_LASTMODIFIED_PARAM) != null) {
325: sendLastModified = Boolean
326: .getBoolean(httpServletRequest
327: .getParameter(BlojsomConstants.OVERRIDE_LASTMODIFIED_PARAM));
328: }
329:
330: // If we have entries, construct a last modified on the most recent entry
331: // Additionally, set the blog date
332: if (sendLastModified) {
333: if ((entries != null) && (entries.length > 0)) {
334: Entry _entry = entries[0];
335: long _lastmodified;
336:
337: if (_entry.getNumComments() > 0) {
338: Comment _comment = _entry.getCommentsAsArray()[_entry
339: .getNumComments() - 1];
340: _lastmodified = _comment.getCommentDate().getTime();
341: if (_logger.isDebugEnabled()) {
342: _logger
343: .debug("Adding last-modified header for most recent entry comment");
344: }
345: } else {
346: _lastmodified = _entry.getDate().getTime();
347: if (_logger.isDebugEnabled()) {
348: _logger
349: .debug("Adding last-modified header for most recent blog entry");
350: }
351: }
352:
353: // Check for the Last-Modified object from one of the plugins
354: if (context
355: .containsKey(BlojsomConstants.BLOJSOM_LAST_MODIFIED)) {
356: Long lastModified = (Long) context
357: .get(BlojsomConstants.BLOJSOM_LAST_MODIFIED);
358: if (lastModified.longValue() > _lastmodified) {
359: _lastmodified = lastModified.longValue();
360: }
361: }
362:
363: // Generates an ETag header based on the string value of LastModified as an ISO8601 Format
364: String etagLastModified = BlojsomUtils
365: .getISO8601Date(new Date(_lastmodified));
366: httpServletResponse.addHeader(
367: BlojsomConstants.HTTP_ETAG, "\""
368: + BlojsomUtils
369: .digestString(etagLastModified)
370: + "\"");
371:
372: httpServletResponse.addDateHeader(
373: BlojsomConstants.HTTP_LASTMODIFIED,
374: _lastmodified);
375: blogdate = entries[0].getRFC822Date();
376: blogISO8601Date = entries[0].getISO8601Date();
377: blogDateObject = entries[0].getDate();
378: blogUTCDate = BlojsomUtils.getUTCDate(entries[0]
379: .getDate());
380: } else {
381: if (_logger.isDebugEnabled()) {
382: _logger
383: .debug("Adding last-modified header for current date");
384: }
385:
386: Date today = new Date();
387: blogdate = BlojsomUtils.getRFC822Date(today);
388: blogISO8601Date = BlojsomUtils.getISO8601Date(today);
389: blogUTCDate = BlojsomUtils.getUTCDate(today);
390: blogDateObject = today;
391: httpServletResponse.addDateHeader(
392: BlojsomConstants.HTTP_LASTMODIFIED, today
393: .getTime());
394:
395: // Generates an ETag header based on the string value of LastModified as an ISO8601 Format
396: httpServletResponse.addHeader(
397: BlojsomConstants.HTTP_ETAG, "\""
398: + BlojsomUtils
399: .digestString(blogISO8601Date)
400: + "\"");
401: }
402: }
403:
404: context.put(BlojsomConstants.BLOJSOM_DATE, blogdate);
405: context.put(BlojsomConstants.BLOJSOM_DATE_ISO8601,
406: blogISO8601Date);
407: context.put(BlojsomConstants.BLOJSOM_DATE_OBJECT,
408: blogDateObject);
409: context.put(BlojsomConstants.BLOJSOM_DATE_UTC, blogUTCDate);
410:
411: // Finish setting up the context for the dispatcher
412: context.put(BlojsomConstants.BLOJSOM_BLOG, blog);
413: context.put(BlojsomConstants.BLOJSOM_ENTRIES, entries);
414: context.put(BlojsomConstants.BLOJSOM_CATEGORIES, categories);
415: context.put(BlojsomConstants.BLOJSOM_VERSION,
416: BlojsomConstants.BLOJSOM_VERSION_NUMBER);
417: context.put(BlojsomConstants.BLOJSOM_BLOG_ID, blog.getBlogId());
418: context.put(BlojsomConstants.BLOJSOM_SITE_URL, blog
419: .getBlogBaseURL());
420:
421: context.put(BlojsomConstants.BLOJSOM_BLOG_ID, blog.getBlogId());
422:
423: String templateAndType = (String) blog.getTemplates().get(
424: flavor);
425: String[] templateData = BlojsomUtils.parseOnlyCommaList(
426: templateAndType, true);
427: String templateExtension = BlojsomUtils
428: .getFileExtension(templateData[0]);
429:
430: Dispatcher dispatcher = null;
431: try {
432: dispatcher = (org.blojsom.dispatcher.Dispatcher) _classPathXmlApplicationContext
433: .getBean(templateExtension);
434: } catch (BeansException e) {
435: if (_logger.isErrorEnabled()) {
436: _logger.error(e);
437: }
438:
439: httpServletResponse.sendError(
440: HttpServletResponse.SC_BAD_REQUEST,
441: "Unable to retrieve dispatcher for template extension: "
442: + templateExtension);
443: }
444:
445: dispatcher.dispatch(httpServletRequest, httpServletResponse,
446: blog, context, templateData[0], templateData[1]);
447: }
448:
449: /**
450: * Take blojsom out of service
451: */
452: public void destroy() {
453: super .destroy();
454:
455: _classPathXmlApplicationContext.destroy();
456:
457: if (_logger.isDebugEnabled()) {
458: _logger.debug("blojsom destroyed");
459: }
460: }
461: }
|