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.plugin.common;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.blojsom.blog.Entry;
035: import org.blojsom.blog.Blog;
036: import org.blojsom.event.Event;
037: import org.blojsom.event.EventBroadcaster;
038: import org.blojsom.event.Listener;
039: import org.blojsom.plugin.Plugin;
040: import org.blojsom.plugin.PluginException;
041: import org.blojsom.plugin.admin.event.ProcessEntryEvent;
042: import org.blojsom.util.BlojsomUtils;
043: import org.blojsom.util.BlojsomConstants;
044:
045: import javax.activation.MimetypesFileTypeMap;
046: import javax.servlet.http.HttpServletRequest;
047: import javax.servlet.http.HttpServletResponse;
048: import javax.servlet.ServletConfig;
049: import javax.servlet.ServletContext;
050: import java.io.File;
051: import java.net.HttpURLConnection;
052: import java.net.URL;
053: import java.text.MessageFormat;
054: import java.text.NumberFormat;
055: import java.text.ParseException;
056: import java.util.*;
057:
058: /**
059: * RSSEnclosurePlugin
060: *
061: * @author David Czarnecki
062: * @version $Id: RSSEnclosurePlugin.java,v 1.6 2007/01/17 02:35:09 czarneckid Exp $
063: * @since blojsom 3.0
064: */
065: public class RSSEnclosurePlugin implements Plugin, Listener {
066:
067: private Log _logger = LogFactory.getLog(RSSEnclosurePlugin.class);
068:
069: private static final String RSS_ENCLOSURE_TEMPLATE = "org/blojsom/plugin/common/templates/admin-rss-enclosure-attachment.vm";
070: private static final String RSS_ENCLOSURE_ATTACHMENT = "RSS_ENCLOSURE_ATTACHMENT";
071: private static final String RSS_ENCLOSURE_URL_ITEM = "RSS_ENCLOSURE_URL_ITEM";
072: private static final String RSS_ENCLOSURE_LENGTH_ITEM = "RSS_ENCLOSURE_LENGTH_ITEM";
073: private static final String RSS_ENCLOSURE_TYPE_ITEM = "RSS_ENCLOSURE_TYPE_ITEM";
074: private static final String RSS_ENCLOSURE_LINK_TEMPLATE = "<enclosure url=\"{0}\" length=\"{1}\" type=\"{2}\" />";
075:
076: protected static final String MIME_TYPE_XMPEGURL = "audio/x-mpegurl m3u";
077: protected static final String MIME_TYPE_XMPEG = "audio/x-mpeg mp1 mp2 mp3 mpa mpega";
078:
079: public static final String DEFAULT_MIME_TYPE = "application/octet-stream";
080: public static final String METADATA_RSS_ENCLOSURE = "rss-enclosure";
081: public static final String METADATA_ESS_ENCLOSURE_OBJECT = "rss-enclosure-object";
082:
083: public static final String RSS_ENCLOSURE_URL = "rss-enclosure-url";
084: public static final String RSS_ENCLOSURE_LENGTH = "rss-enclosure-length";
085: public static final String RSS_ENCLOSURE_TYPE = "rss-enclosure-type";
086:
087: protected EventBroadcaster _eventBroadcaster;
088: protected ServletConfig _servletConfig;
089: protected Properties _blojsomProperties;
090:
091: protected String _enclosuresDirectory;
092:
093: /**
094: * Default constructor
095: */
096: public RSSEnclosurePlugin() {
097: }
098:
099: /**
100: * Set the default blojsom properties
101: *
102: * @param blojsomProperties Default blojsom properties
103: */
104: public void setBlojsomProperties(Properties blojsomProperties) {
105: _blojsomProperties = blojsomProperties;
106: }
107:
108: /**
109: * Set the {@link ServletConfig}
110: *
111: * @param servletConfig {@link ServletConfig}
112: */
113: public void setServletConfig(ServletConfig servletConfig) {
114: _servletConfig = servletConfig;
115: }
116:
117: /**
118: * Set the {@link EventBroadcaster}
119: *
120: * @param eventBroadcaster {@link EventBroadcaster}
121: */
122: public void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
123: _eventBroadcaster = eventBroadcaster;
124: }
125:
126: /**
127: * Set the directory for enclosures
128: *
129: * @param enclosuresDirectory Enclosures directory
130: */
131: public void setEnclosuresDirectory(String enclosuresDirectory) {
132: _enclosuresDirectory = enclosuresDirectory;
133: }
134:
135: /**
136: * Initialize this plugin. This method only called when the plugin is
137: * instantiated.
138: *
139: * @throws org.blojsom.plugin.PluginException
140: * If there is an error initializing the plugin
141: */
142: public void init() throws PluginException {
143: _eventBroadcaster.addListener(this );
144:
145: if (BlojsomUtils.checkNullOrBlank(_enclosuresDirectory)) {
146: _enclosuresDirectory = _blojsomProperties.getProperty(
147: BlojsomConstants.RESOURCES_DIRECTORY_IP,
148: BlojsomConstants.DEFAULT_RESOURCES_DIRECTORY);
149: }
150:
151: _logger.debug("Initialized RSS enclosures plugin");
152: }
153:
154: /**
155: * Add additional mime types to the map
156: *
157: * @param mimeTypes Mime types map
158: */
159: protected void addAdditionalMimeTypes(MimetypesFileTypeMap mimeTypes) {
160: mimeTypes.addMimeTypes(MIME_TYPE_XMPEGURL);
161: mimeTypes.addMimeTypes(MIME_TYPE_XMPEG);
162: }
163:
164: /**
165: * Process the blog entries
166: *
167: * @param httpServletRequest Request
168: * @param httpServletResponse Response
169: * @param blog {@link Blog} instance
170: * @param context Context
171: * @param entries Blog entries retrieved for the particular request
172: * @return Modified set of blog entries
173: * @throws PluginException If there is an error processing the blog entries
174: */
175: public Entry[] process(HttpServletRequest httpServletRequest,
176: HttpServletResponse httpServletResponse, Blog blog,
177: Map context, Entry[] entries) throws PluginException {
178: ServletContext servletContext = _servletConfig
179: .getServletContext();
180:
181: for (int i = 0; i < entries.length; i++) {
182: Entry entry = entries[i];
183: if (BlojsomUtils.checkMapForKey(entry.getMetaData(),
184: RSS_ENCLOSURE_URL)
185: && BlojsomUtils.checkMapForKey(entry.getMetaData(),
186: RSS_ENCLOSURE_LENGTH)
187: && BlojsomUtils.checkMapForKey(entry.getMetaData(),
188: RSS_ENCLOSURE_TYPE)) {
189: String rssEnclosureURL = (String) entry.getMetaData()
190: .get(RSS_ENCLOSURE_URL);
191: rssEnclosureURL = BlojsomUtils
192: .escapeBrackets(rssEnclosureURL);
193:
194: long rssEnclosureLength = -1;
195: try {
196: NumberFormat numberFormat = NumberFormat
197: .getNumberInstance(Locale.US);
198: rssEnclosureLength = numberFormat.parse(
199: (String) entry.getMetaData().get(
200: RSS_ENCLOSURE_LENGTH)).longValue();
201: } catch (ParseException e) {
202: }
203: String rssEnclosureType = (String) entry.getMetaData()
204: .get(RSS_ENCLOSURE_TYPE);
205:
206: String rssEnclosure = MessageFormat.format(
207: RSS_ENCLOSURE_LINK_TEMPLATE, new Object[] {
208: rssEnclosureURL,
209: Long.toString(rssEnclosureLength),
210: rssEnclosureType });
211: RSSEnclosure rssEnclosureObject = new RSSEnclosure(
212: rssEnclosureURL, rssEnclosureLength,
213: rssEnclosureType);
214:
215: entry.getMetaData().put(METADATA_RSS_ENCLOSURE,
216: rssEnclosure);
217: entry.getMetaData().put(METADATA_ESS_ENCLOSURE_OBJECT,
218: rssEnclosureObject);
219: if (_logger.isDebugEnabled()) {
220: _logger.debug("Added explicit enclosure: "
221: + rssEnclosure);
222: }
223: } else {
224: if (BlojsomUtils.checkMapForKey(entry.getMetaData(),
225: METADATA_RSS_ENCLOSURE)) {
226: String enclosureName = BlojsomUtils
227: .getFilenameFromPath((String) entry
228: .getMetaData().get(
229: METADATA_RSS_ENCLOSURE));
230: File enclosure = new File(servletContext
231: .getRealPath("/")
232: + _enclosuresDirectory
233: + blog.getBlogId()
234: + "/" + enclosureName);
235: if (enclosure.exists()) {
236: if (_logger.isDebugEnabled()) {
237: _logger
238: .debug("Adding enclosure to entry for file: "
239: + enclosureName);
240: }
241:
242: MimetypesFileTypeMap mimetypesFileTypeMap = new MimetypesFileTypeMap();
243: addAdditionalMimeTypes(mimetypesFileTypeMap);
244: String type = mimetypesFileTypeMap
245: .getContentType(enclosure);
246:
247: StringBuffer enclosureElement = new StringBuffer();
248: String url = blog.getBlogBaseURL()
249: + _enclosuresDirectory
250: + blog.getBlogId() + "/"
251: + enclosure.getName();
252: url = BlojsomUtils.escapeBrackets(url);
253: enclosureElement.append("<enclosure url=\"");
254: enclosureElement.append(url);
255: enclosureElement.append("\" length=\"");
256: enclosureElement.append(enclosure.length());
257: enclosureElement.append("\" type=\"");
258: if (BlojsomUtils.checkNullOrBlank(type)) {
259: type = DEFAULT_MIME_TYPE;
260: }
261: enclosureElement.append(type);
262: enclosureElement.append("\" />");
263:
264: RSSEnclosure rssEnclosure = new RSSEnclosure(
265: url, enclosure.length(), type);
266: entry.getMetaData().put(METADATA_RSS_ENCLOSURE,
267: enclosureElement.toString());
268: entry.getMetaData().put(
269: METADATA_ESS_ENCLOSURE_OBJECT,
270: rssEnclosure);
271: if (_logger.isDebugEnabled()) {
272: _logger.debug("Added enclosure: "
273: + enclosureElement.toString());
274: }
275: }
276: }
277: }
278: }
279:
280: return entries;
281: }
282:
283: /**
284: * Perform any cleanup for the plugin. Called after {@link #process}.
285: *
286: * @throws org.blojsom.plugin.PluginException
287: * If there is an error performing cleanup for this plugin
288: */
289: public void cleanup() throws PluginException {
290: }
291:
292: /**
293: * Called when BlojsomServlet is taken out of service
294: *
295: * @throws org.blojsom.plugin.PluginException
296: * If there is an error in finalizing this plugin
297: */
298: public void destroy() throws PluginException {
299: }
300:
301: /**
302: * Handle an event broadcast from another component
303: *
304: * @param event {@link org.blojsom.event.Event} to be handled
305: */
306: public void handleEvent(Event event) {
307: }
308:
309: /**
310: * Process an event from another component
311: *
312: * @param event {@link org.blojsom.event.Event} to be handled
313: */
314: public void processEvent(Event event) {
315: if (event instanceof ProcessEntryEvent) {
316: if (_logger.isDebugEnabled()) {
317: _logger.debug("Handling process blog entry event");
318: }
319:
320: ProcessEntryEvent processBlogEntryEvent = (ProcessEntryEvent) event;
321: String blogId = processBlogEntryEvent.getBlog().getBlogId();
322:
323: Map templateAdditions = (Map) processBlogEntryEvent
324: .getContext().get("BLOJSOM_TEMPLATE_ADDITIONS");
325: if (templateAdditions == null) {
326: templateAdditions = new TreeMap();
327: }
328:
329: templateAdditions.put(getClass().getName(), "#parse('"
330: + RSS_ENCLOSURE_TEMPLATE + "')");
331: processBlogEntryEvent.getContext().put(
332: "BLOJSOM_TEMPLATE_ADDITIONS", templateAdditions);
333:
334: // Create a list of files in the user's resource directory
335: File resourceDirectory = new File(_servletConfig
336: .getServletContext().getRealPath("/")
337: + _enclosuresDirectory + blogId + "/");
338: Map resourceFilesMap = null;
339: if (resourceDirectory.exists()) {
340: File[] resourceFiles = resourceDirectory.listFiles();
341:
342: if (resourceFiles != null) {
343: resourceFilesMap = new HashMap(resourceFiles.length);
344: for (int i = 0; i < resourceFiles.length; i++) {
345: File resourceFile = resourceFiles[i];
346: resourceFilesMap.put(resourceFile.getName(),
347: resourceFile.getName());
348: }
349: }
350: } else {
351: resourceFilesMap = new HashMap();
352: }
353:
354: // Preserve the current rss enclosure if none submitted
355: if (processBlogEntryEvent.getEntry() != null) {
356: String currentEnclosure = (String) processBlogEntryEvent
357: .getEntry().getMetaData().get(
358: METADATA_RSS_ENCLOSURE);
359: String currentEnclosureURL = (String) processBlogEntryEvent
360: .getEntry().getMetaData()
361: .get(RSS_ENCLOSURE_URL);
362: String currentEnclosureLength = (String) processBlogEntryEvent
363: .getEntry().getMetaData().get(
364: RSS_ENCLOSURE_LENGTH);
365: String currentEnclosureType = (String) processBlogEntryEvent
366: .getEntry().getMetaData().get(
367: RSS_ENCLOSURE_TYPE);
368:
369: if (_logger.isDebugEnabled()) {
370: _logger.debug("Current enclosure: "
371: + currentEnclosure);
372: }
373: processBlogEntryEvent.getContext().put(
374: RSS_ENCLOSURE_ATTACHMENT, currentEnclosure);
375: processBlogEntryEvent.getContext().put(
376: RSS_ENCLOSURE_URL_ITEM,
377: BlojsomUtils
378: .escapeBrackets(currentEnclosureURL));
379: processBlogEntryEvent.getContext().put(
380: RSS_ENCLOSURE_LENGTH_ITEM,
381: currentEnclosureLength);
382: processBlogEntryEvent.getContext().put(
383: RSS_ENCLOSURE_TYPE_ITEM, currentEnclosureType);
384: }
385:
386: String rssEnclosure = BlojsomUtils.getRequestValue(
387: METADATA_RSS_ENCLOSURE, processBlogEntryEvent
388: .getHttpServletRequest());
389: String rssEnclosureURL = BlojsomUtils.getRequestValue(
390: RSS_ENCLOSURE_URL, processBlogEntryEvent
391: .getHttpServletRequest());
392: String rssEnclosureLength = BlojsomUtils.getRequestValue(
393: RSS_ENCLOSURE_LENGTH, processBlogEntryEvent
394: .getHttpServletRequest());
395: String rssEnclosureType = BlojsomUtils.getRequestValue(
396: RSS_ENCLOSURE_TYPE, processBlogEntryEvent
397: .getHttpServletRequest());
398:
399: if (!BlojsomUtils.checkNullOrBlank(rssEnclosureURL)
400: && (processBlogEntryEvent.getEntry() != null)) {
401: processBlogEntryEvent.getEntry().getMetaData().put(
402: RSS_ENCLOSURE_URL, rssEnclosureURL);
403: if (BlojsomUtils.checkNullOrBlank(rssEnclosureLength)
404: && BlojsomUtils
405: .checkNullOrBlank(rssEnclosureType)) {
406: String[] enclosureProperties = discoverEnclosureProperties(rssEnclosureURL);
407: rssEnclosureLength = enclosureProperties[0];
408: rssEnclosureType = enclosureProperties[1];
409: }
410:
411: processBlogEntryEvent.getEntry().getMetaData().put(
412: RSS_ENCLOSURE_LENGTH, rssEnclosureLength);
413: processBlogEntryEvent.getEntry().getMetaData().put(
414: RSS_ENCLOSURE_TYPE, rssEnclosureType);
415: processBlogEntryEvent.getContext().put(
416: RSS_ENCLOSURE_URL_ITEM,
417: BlojsomUtils.escapeBrackets(rssEnclosureURL));
418: processBlogEntryEvent.getContext().put(
419: RSS_ENCLOSURE_LENGTH_ITEM, rssEnclosureLength);
420: processBlogEntryEvent.getContext().put(
421: RSS_ENCLOSURE_TYPE_ITEM, rssEnclosureType);
422:
423: if (_logger.isDebugEnabled()) {
424: _logger
425: .debug("Added/updated RSS enclosure (explict): "
426: + rssEnclosureURL);
427: }
428: } else {
429: if (processBlogEntryEvent.getEntry() != null) {
430: processBlogEntryEvent.getEntry().getMetaData()
431: .remove(RSS_ENCLOSURE_URL);
432: processBlogEntryEvent.getEntry().getMetaData()
433: .remove(RSS_ENCLOSURE_TYPE);
434: processBlogEntryEvent.getEntry().getMetaData()
435: .remove(RSS_ENCLOSURE_LENGTH);
436: }
437:
438: processBlogEntryEvent.getContext().remove(
439: RSS_ENCLOSURE_URL_ITEM);
440: processBlogEntryEvent.getContext().remove(
441: RSS_ENCLOSURE_TYPE_ITEM);
442: processBlogEntryEvent.getContext().remove(
443: RSS_ENCLOSURE_LENGTH_ITEM);
444:
445: if (!BlojsomUtils.checkNullOrBlank(rssEnclosure)
446: && processBlogEntryEvent.getEntry() != null) {
447: processBlogEntryEvent.getEntry().getMetaData().put(
448: METADATA_RSS_ENCLOSURE, rssEnclosure);
449: processBlogEntryEvent.getContext().put(
450: RSS_ENCLOSURE_ATTACHMENT, rssEnclosure);
451:
452: if (_logger.isDebugEnabled()) {
453: _logger
454: .debug("Added/updated RSS enclosure: "
455: + BlojsomUtils
456: .getFilenameFromPath(rssEnclosure));
457: }
458: } else {
459: if (processBlogEntryEvent.getEntry() != null) {
460: processBlogEntryEvent.getEntry().getMetaData()
461: .remove(METADATA_RSS_ENCLOSURE);
462: }
463: processBlogEntryEvent.getContext().remove(
464: RSS_ENCLOSURE_ATTACHMENT);
465: }
466: }
467:
468: resourceFilesMap = new TreeMap(resourceFilesMap);
469: processBlogEntryEvent.getContext().put(
470: "PLUGIN_RSS_ENCLOSURE_FILES", resourceFilesMap);
471: }
472: }
473:
474: /**
475: * Discover the content length and content type for an enclosure URL
476: *
477: * @param rssEnclosureURL URL for enclosure
478: * @return String array containing the enclosure's content length and content type
479: */
480: protected String[] discoverEnclosureProperties(
481: String rssEnclosureURL) {
482: String[] enclosureProperties = new String[] { "", "" };
483:
484: try {
485: if (!rssEnclosureURL.toLowerCase().startsWith("http://")) {
486: if (_logger.isDebugEnabled()) {
487: _logger
488: .debug("RSS enclosure URL not an HTTP-accessible resource");
489: }
490: } else {
491: URL enclosure = new URL(rssEnclosureURL);
492: HttpURLConnection httpURLConnection = (HttpURLConnection) enclosure
493: .openConnection();
494: httpURLConnection.setRequestMethod("HEAD");
495: httpURLConnection.connect();
496:
497: enclosureProperties[0] = Integer
498: .toString(httpURLConnection.getContentLength());
499: enclosureProperties[1] = httpURLConnection
500: .getContentType();
501:
502: httpURLConnection.disconnect();
503: }
504: } catch (Exception e) {
505: if (_logger.isErrorEnabled()) {
506: _logger.error("Error retrieving enclosure properties",
507: e);
508: }
509: }
510:
511: return enclosureProperties;
512: }
513:
514: /**
515: * RSS Enclosure
516: *
517: * @author David Czarnecki
518: */
519: public class RSSEnclosure {
520:
521: private String url;
522: private long length;
523: private String type;
524:
525: /**
526: * Construct an RSS enclosure
527: *
528: * @param url URL to retrieve enclosure
529: * @param length Length of enclosure
530: * @param type Type of enclosure
531: */
532: public RSSEnclosure(String url, long length, String type) {
533: this .url = url;
534: this .length = length;
535: this .type = type;
536: }
537:
538: /**
539: * Get the URL for the enclosure
540: *
541: * @return URL for enclosure
542: */
543: public String getUrl() {
544: return url;
545: }
546:
547: /**
548: * Get the length of the enclosure
549: *
550: * @return Length of enclosure
551: */
552: public long getLength() {
553: return length;
554: }
555:
556: /**
557: * Get the type of the enclosure
558: *
559: * @return Type of enclosure (e.g. audio/mpeg)
560: */
561: public String getType() {
562: return type;
563: }
564: }
565: }
|