001: /*---------------------------------------------------------------------------*\
002: $Id: FileOutputHandler.java 7041 2007-09-09 01:04:47Z bmc $
003: ---------------------------------------------------------------------------
004: This software is released under a BSD-style license:
005:
006: Copyright (c) 2004-2007 Brian M. Clapper. All rights reserved.
007:
008: Redistribution and use in source and binary forms, with or without
009: modification, are permitted provided that the following conditions are
010: met:
011:
012: 1. Redistributions of source code must retain the above copyright notice,
013: this list of conditions and the following disclaimer.
014:
015: 2. The end-user documentation included with the redistribution, if any,
016: must include the following acknowlegement:
017:
018: "This product includes software developed by Brian M. Clapper
019: (bmc@clapper.org, http://www.clapper.org/bmc/). That software is
020: copyright (c) 2004-2007 Brian M. Clapper."
021:
022: Alternately, this acknowlegement may appear in the software itself,
023: if wherever such third-party acknowlegements normally appear.
024:
025: 3. Neither the names "clapper.org", "curn", nor any of the names of the
026: project contributors may be used to endorse or promote products
027: derived from this software without prior written permission. For
028: written permission, please contact bmc@clapper.org.
029:
030: 4. Products derived from this software may not be called "curn", nor may
031: "clapper.org" appear in their names without prior written permission
032: of Brian M. Clapper.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
036: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
037: NO EVENT SHALL BRIAN M. CLAPPER BE LIABLE FOR ANY DIRECT, INDIRECT,
038: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
039: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
040: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
041: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044: \*---------------------------------------------------------------------------*/
045:
046: package org.clapper.curn.output;
047:
048: import org.clapper.curn.Constants;
049: import org.clapper.curn.CurnConfig;
050: import org.clapper.curn.ConfiguredOutputHandler;
051: import org.clapper.curn.CurnException;
052: import org.clapper.curn.FeedInfo;
053: import org.clapper.curn.OutputHandler;
054: import org.clapper.curn.CurnUtil;
055: import org.clapper.curn.parser.RSSChannel;
056:
057: import org.clapper.util.config.ConfigurationException;
058: import org.clapper.util.config.NoSuchSectionException;
059: import org.clapper.util.io.IOExceptionExt;
060: import org.clapper.util.logging.Logger;
061:
062: import java.io.IOException;
063: import java.io.File;
064: import java.io.PrintWriter;
065:
066: /**
067: * <p><tt>FileOutputHandler</tt> is an abstract base class for
068: * <tt>OutputHandler</tt> subclasses that write RSS feed summaries to a
069: * file. It consolidates common logic and configuration handling for such
070: * classes, providing both consistent implementation and configuration.
071: *
072: * @see OutputHandler
073: * @see org.clapper.curn.Curn
074: * @see org.clapper.curn.parser.RSSChannel
075: *
076: * @version <tt>$Revision: 7041 $</tt>
077: */
078: public abstract class FileOutputHandler implements OutputHandler {
079: /*----------------------------------------------------------------------*\
080: Public Constants
081: \*----------------------------------------------------------------------*/
082:
083: /**
084: * Configuration variable: encoding
085: */
086: public static final String CFG_ENCODING = "Encoding";
087:
088: /**
089: * Whether or not to show curn information
090: */
091: public static final String CFG_SHOW_CURN_INFO = "ShowCurnInfo";
092:
093: /**
094: * Where to save the output, if any
095: */
096: public static final String CFG_SAVE_AS = "SaveAs";
097:
098: /**
099: * Whether we're ONLY saving output
100: */
101: public static final String CFG_SAVE_ONLY = "SaveOnly";
102:
103: /**
104: * Number of backups of saved files to keep.
105: */
106: public static final String CFG_SAVED_BACKUPS = "SavedBackups";
107:
108: /*----------------------------------------------------------------------*\
109: Public Constants
110: \*----------------------------------------------------------------------*/
111:
112: /**
113: * Default encoding value
114: */
115: private static final String DEFAULT_CHARSET_ENCODING = "utf-8";
116:
117: /*----------------------------------------------------------------------*\
118: Private Instance Data
119: \*----------------------------------------------------------------------*/
120:
121: private String name = null;
122: private File outputFile = null;
123: private CurnConfig config = null;
124: private ConfiguredOutputHandler cfgHandler = null;
125: private boolean saveOnly = false;
126: private boolean showToolInfo = true;
127: private int savedBackups = 0;
128: private String encoding = null;
129:
130: /**
131: * For logging
132: */
133: private Logger log = null;
134:
135: /*----------------------------------------------------------------------*\
136: Constructor
137: \*----------------------------------------------------------------------*/
138:
139: /**
140: * Construct a new <tt>FileOutputHandler</tt>
141: */
142: public FileOutputHandler() {
143: // Nothing to do.
144: }
145:
146: /*----------------------------------------------------------------------*\
147: Public Methods
148: \*----------------------------------------------------------------------*/
149:
150: /**
151: * Get the name of this output handler. The name must be unique.
152: *
153: * @return the name
154: */
155: public String getName() {
156: return name;
157: }
158:
159: /**
160: * Set the name of this output handler. Called by <i>curn</i>.
161: *
162: * @param name the name
163: *
164: * @throws CurnException on error
165: */
166: public void setName(final String name) throws CurnException {
167: this .name = name;
168: }
169:
170: /**
171: * Initializes the output handler for another set of RSS channels.
172: *
173: * @param config the parsed <i>curn</i> configuration data
174: * @param cfgHandler the <tt>ConfiguredOutputHandler</tt> wrapper
175: * containing this object; the wrapper has some useful
176: * metadata, such as the object's configuration section
177: * name and extra variables.
178: *
179: * @throws ConfigurationException configuration error
180: * @throws CurnException some other initialization error
181: */
182: public final void init(final CurnConfig config,
183: final ConfiguredOutputHandler cfgHandler)
184: throws ConfigurationException, CurnException {
185: String saveAs = null;
186: String sectionName = null;
187:
188: this .config = config;
189: sectionName = cfgHandler.getSectionName();
190: this .name = sectionName;
191:
192: initLogger();
193: try {
194: if (sectionName != null) {
195: saveAs = config.getOptionalStringValue(sectionName,
196: CFG_SAVE_AS, null);
197: savedBackups = config.getOptionalCardinalValue(
198: sectionName, CFG_SAVED_BACKUPS, 0);
199: saveOnly = config.getOptionalBooleanValue(sectionName,
200: CFG_SAVE_ONLY, false);
201:
202: showToolInfo = config.getOptionalBooleanValue(
203: sectionName, CFG_SHOW_CURN_INFO, true);
204: encoding = config.getOptionalStringValue(sectionName,
205: CFG_ENCODING, DEFAULT_CHARSET_ENCODING);
206:
207: // saveOnly cannot be set unless saveAs is non-null. The
208: // CurnConfig class is supposed to trap for this, so an
209: // assertion is fine here.
210:
211: assert ((!saveOnly) || (saveAs != null));
212: }
213: }
214:
215: catch (NoSuchSectionException ex) {
216: throw new ConfigurationException(ex);
217: }
218:
219: if (saveAs != null)
220: outputFile = CurnUtil.mapConfiguredPathName(saveAs);
221:
222: else {
223: try {
224: outputFile = File.createTempFile("curn", null);
225: outputFile.deleteOnExit();
226: }
227:
228: catch (IOException ex) {
229: throw new CurnException(Constants.BUNDLE_NAME,
230: "FileOutputHandler.cantMakeTempFile",
231: "Cannot create temporary file", ex);
232: }
233: }
234:
235: log.debug("Calling " + this .getClass().getName()
236: + ".initOutputHandler()");
237:
238: this .cfgHandler = cfgHandler;
239: initOutputHandler(config, cfgHandler);
240: }
241:
242: /**
243: * Perform any subclass-specific initialization. Subclasses must
244: * override this method.
245: *
246: * @param config the parsed <i>curn</i> configuration data
247: * @param cfgHandler the <tt>ConfiguredOutputHandler</tt> wrapper
248: * containing this object; the wrapper has some useful
249: * metadata, such as the object's configuration section
250: * name and extra variables.
251: *
252: * @throws ConfigurationException configuration error
253: * @throws CurnException some other initialization error
254: */
255: public abstract void initOutputHandler(CurnConfig config,
256: ConfiguredOutputHandler cfgHandler)
257: throws ConfigurationException, CurnException;
258:
259: /**
260: * Display the list of <tt>RSSItem</tt> news items to whatever output
261: * is defined for the underlying class. Output should be written to the
262: * <tt>PrintWriter</tt> that was passed to the {@link #init init()} method.
263: *
264: * @param channel The parsed channel data
265: * @param feedInfo The feed.
266: *
267: * @throws CurnException unable to write output
268: */
269: public abstract void displayChannel(RSSChannel channel,
270: FeedInfo feedInfo) throws CurnException;
271:
272: /**
273: * Flush any buffered-up output.
274: *
275: * @throws CurnException unable to write output
276: */
277: public abstract void flush() throws CurnException;
278:
279: /**
280: * Get the content (i.e., MIME) type for output produced by this output
281: * handler.
282: *
283: * @return the content type
284: */
285: public abstract String getContentType();
286:
287: /**
288: * Get the <tt>File</tt> that represents the output produced by the
289: * handler, if applicable. (Use of a <tt>File</tt>, rather than an
290: * <tt>InputStream</tt>, is more efficient when mailing the output,
291: * since the email API ultimately wants files and will create
292: * temporary files for <tt>InputStream</tt>s.)
293: *
294: * @return the output file, or null if no suitable output was produced
295: *
296: * @throws CurnException an error occurred
297: */
298: public final File getGeneratedOutput() throws CurnException {
299: return hasGeneratedOutput() ? outputFile : null;
300: }
301:
302: /**
303: * Get the output encoding.
304: *
305: * @return the encoding
306: */
307: public String getOutputEncoding() {
308: return encoding;
309: }
310:
311: /**
312: * Determine whether this handler has produced any actual output (i.e.,
313: * whether {@link #getGeneratedOutput()} will return a non-null
314: * <tt>File</tt> if called).
315: *
316: * @return <tt>true</tt> if the handler has produced output,
317: * <tt>false</tt> if not
318: *
319: * @see #getGeneratedOutput
320: * @see #getContentType
321: */
322: public final boolean hasGeneratedOutput() {
323: boolean hasOutput = false;
324:
325: if ((!saveOnly) && (outputFile != null)) {
326: long len = outputFile.length();
327: log.debug("outputFile=" + outputFile.getPath() + ", size="
328: + len);
329:
330: hasOutput = (len > 0);
331: }
332:
333: log.debug("hasGeneratedOutput? " + hasOutput);
334: return hasOutput;
335: }
336:
337: /**
338: * Make a copy of the output handler.
339: *
340: * @return a clean, initialized copy of the output handler
341: *
342: * @throws CurnException on error
343: */
344: public final OutputHandler makeCopy() throws CurnException {
345: Class cls = this .getClass();
346: FileOutputHandler copy = null;
347:
348: try {
349: copy = (FileOutputHandler) cls.newInstance();
350: copy.name = name;
351: copy.config = config;
352: copy.outputFile = File.createTempFile("curn", null);
353: copy.outputFile.deleteOnExit();
354: copy.cfgHandler = cfgHandler;
355: copy.saveOnly = saveOnly;
356: copy.showToolInfo = showToolInfo;
357: copy.savedBackups = 0;
358: copy.encoding = encoding;
359: copy.initLogger();
360: copySubclassFields(copy);
361: copy.initOutputHandler(config, cfgHandler);
362: }
363:
364: catch (Exception ex) {
365: throw new CurnException("Can't copy instance of \""
366: + cls.toString() + "\"", ex);
367: }
368:
369: return copy;
370: }
371:
372: /*----------------------------------------------------------------------*\
373: Protected Methods
374: \*----------------------------------------------------------------------*/
375:
376: /**
377: * Copy any subclass fields into a copy of this output handler.
378: * Default version of this method does nothing.
379: *
380: * @param theCopy copy of this class
381: *
382: * @throws CurnException on error
383: */
384: protected void copySubclassFields(FileOutputHandler theCopy) {
385: }
386:
387: /**
388: * Get the output file.
389: *
390: * @return the output file, or none if not created yet
391: */
392: protected final File getOutputFile() {
393: return outputFile;
394: }
395:
396: /**
397: * Open the output file, returning a <tt>PrintWriter</tt>. Handles
398: * whether or not to roll the saved file, etc.
399: *
400: * @return the <tt>PrintWriter</tt>
401: *
402: * @throws CurnException unable to open file
403: */
404: protected PrintWriter openOutputFile() throws CurnException {
405: PrintWriter w = null;
406:
407: try {
408: assert (outputFile != null);
409: log.debug("Opening output file \"" + outputFile + "\"");
410:
411: // For the output handler output file, the index marker between
412: // the file name and the extension, rather than at the end of
413: // the file (since the extension is likely to matter).
414:
415: w = new PrintWriter(CurnUtil.openOutputFile(outputFile,
416: encoding, CurnUtil.IndexMarker.BEFORE_EXTENSION,
417: savedBackups));
418: }
419:
420: catch (IOExceptionExt ex) {
421: throw new CurnException(ex);
422: }
423:
424: return w;
425: }
426:
427: /**
428: * Determine whether the handler is saving output only, or also reporting
429: * output to <i>curn</i>.
430: *
431: * @return <tt>true</tt> if saving output only, <tt>false</tt> if also
432: * reporting File.createTempFile("curn", null);
433: outputFile.deleteOnExit();output to <i>curn</i>
434: */
435: protected final boolean savingOutputOnly() {
436: return saveOnly;
437: }
438:
439: /**
440: * Override the encoding specified by the {@link #CFG_ENCODING}
441: * configuration parameter. To have any effect, this method must be
442: * called before {@link #openOutputFile}
443: *
444: * @param newEncoding the new encoding, or null to use the default
445: *
446: * @see #getOutputEncoding
447: */
448: protected final void setOutputEncoding(final String newEncoding) {
449: this .encoding = newEncoding;
450: }
451:
452: /**
453: * Determine whether or not to display curn tool-related information in
454: * the generated output. Subclasses are not required to display
455: * tool-related information in the generated output, but if they do,
456: * they are strongly encouraged to do so conditionally, based on the
457: * value of this configuration item.
458: *
459: * @return <tt>true</tt> if tool-related information is to be displayed
460: * (assuming the output handler supports it), or <tt>false</tt>
461: * if tool-related information should be suppressed.
462: */
463: protected final boolean displayToolInfo() {
464: return this .showToolInfo;
465: }
466:
467: /*----------------------------------------------------------------------*\
468: Private Methods
469: \*----------------------------------------------------------------------*/
470:
471: private void initLogger() {
472: log = new Logger(getClass().getName() + "." + name);
473: }
474: }
|