001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.attachment;
021:
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.util.*;
027:
028: import org.apache.log4j.Logger;
029:
030: import com.ecyrd.jspwiki.*;
031: import com.ecyrd.jspwiki.parser.MarkupParser;
032: import com.ecyrd.jspwiki.providers.ProviderException;
033: import com.ecyrd.jspwiki.providers.WikiAttachmentProvider;
034: import com.ecyrd.jspwiki.util.ClassUtil;
035: import com.opensymphony.oscache.base.Cache;
036: import com.opensymphony.oscache.base.NeedsRefreshException;
037:
038: /**
039: * Provides facilities for handling attachments. All attachment
040: * handling goes through this class.
041: * <p>
042: * The AttachmentManager provides a facade towards the current WikiAttachmentProvider
043: * that is in use. It is created by the WikiEngine as a singleton object, and
044: * can be requested through the WikiEngine.
045: *
046: * @author Janne Jalkanen
047: * @since 1.9.28
048: */
049: public class AttachmentManager {
050: /**
051: * The property name for defining the attachment provider class name.
052: */
053: public static final String PROP_PROVIDER = "jspwiki.attachmentProvider";
054:
055: /**
056: * The maximum size of attachments that can be uploaded.
057: */
058: public static final String PROP_MAXSIZE = "jspwiki.attachment.maxsize";
059:
060: /**
061: * A space-separated list of attachment types which can be uploaded
062: */
063: public static final String PROP_ALLOWEDEXTENSIONS = "jspwiki.attachment.allowed";
064:
065: /**
066: * A space-separated list of attachment types which cannot be uploaded
067: */
068: public static final String PROP_FORDBIDDENEXTENSIONS = "jspwiki.attachment.forbidden";
069:
070: static Logger log = Logger.getLogger(AttachmentManager.class);
071: private WikiAttachmentProvider m_provider;
072: private WikiEngine m_engine;
073:
074: /**
075: * Creates a new AttachmentManager. Note that creation will never fail,
076: * but it's quite likely that attachments do not function.
077: * <p>
078: * <b>DO NOT CREATE</b> an AttachmentManager on your own, unless you really
079: * know what you're doing. Just use WikiEngine.getAttachmentManager() if
080: * you're making a module for JSPWiki.
081: *
082: * @param engine The wikiengine that owns this attachment manager.
083: * @param props A list of properties from which the AttachmentManager will seek
084: * its configuration. Typically this is the "jspwiki.properties".
085: */
086:
087: // FIXME: Perhaps this should fail somehow.
088: public AttachmentManager(WikiEngine engine, Properties props) {
089: String classname;
090:
091: m_engine = engine;
092:
093: //
094: // If user wants to use a cache, then we'll use the CachingProvider.
095: //
096: boolean useCache = "true".equals(props
097: .getProperty(PageManager.PROP_USECACHE));
098:
099: if (useCache) {
100: classname = "com.ecyrd.jspwiki.providers.CachingAttachmentProvider";
101: } else {
102: classname = props.getProperty(PROP_PROVIDER);
103: }
104:
105: //
106: // If no class defined, then will just simply fail.
107: //
108: if (classname == null) {
109: log
110: .info("No attachment provider defined - disabling attachment support.");
111: return;
112: }
113:
114: //
115: // Create and initialize the provider.
116: //
117: try {
118: Class providerclass = ClassUtil.findClass(
119: "com.ecyrd.jspwiki.providers", classname);
120:
121: m_provider = (WikiAttachmentProvider) providerclass
122: .newInstance();
123:
124: m_provider.initialize(m_engine, props);
125: } catch (ClassNotFoundException e) {
126: log.error("Attachment provider class not found", e);
127: } catch (InstantiationException e) {
128: log.error("Attachment provider could not be created", e);
129: } catch (IllegalAccessException e) {
130: log.error(
131: "You may not access the attachment provider class",
132: e);
133: } catch (NoRequiredPropertyException e) {
134: log.error(
135: "Attachment provider did not find a property that it needed: "
136: + e.getMessage(), e);
137: m_provider = null; // No, it did not work.
138: } catch (IOException e) {
139: log.error("Attachment provider reports IO error", e);
140: m_provider = null;
141: }
142: }
143:
144: /**
145: * Returns true, if attachments are enabled and running.
146: *
147: * @return A boolean value indicating whether attachment functionality is enabled.
148: */
149: public boolean attachmentsEnabled() {
150: return m_provider != null;
151: }
152:
153: /**
154: * Gets info on a particular attachment, latest version.
155: *
156: * @param name A full attachment name.
157: * @return Attachment, or null, if no such attachment exists.
158: * @throws ProviderException If something goes wrong.
159: */
160: public Attachment getAttachmentInfo(String name)
161: throws ProviderException {
162: return getAttachmentInfo(name, WikiProvider.LATEST_VERSION);
163: }
164:
165: /**
166: * Gets info on a particular attachment with the given version.
167: *
168: * @param name A full attachment name.
169: * @param version A version number.
170: * @return Attachment, or null, if no such attachment or version exists.
171: * @throws ProviderException If something goes wrong.
172: */
173:
174: public Attachment getAttachmentInfo(String name, int version)
175: throws ProviderException {
176: if (name == null) {
177: return null;
178: }
179:
180: return getAttachmentInfo(null, name, version);
181: }
182:
183: /**
184: * Figures out the full attachment name from the context and
185: * attachment name.
186: *
187: * @param context The current WikiContext
188: * @param attachmentname The file name of the attachment.
189: * @return Attachment, or null, if no such attachment exists.
190: * @throws ProviderException If something goes wrong.
191: */
192:
193: public Attachment getAttachmentInfo(WikiContext context,
194: String attachmentname) throws ProviderException {
195: return getAttachmentInfo(context, attachmentname,
196: WikiProvider.LATEST_VERSION);
197: }
198:
199: /**
200: * Figures out the full attachment name from the context and
201: * attachment name.
202: *
203: * @param context The current WikiContext
204: * @param attachmentname The file name of the attachment.
205: * @param version A particular version.
206: * @return Attachment, or null, if no such attachment or version exists.
207: * @throws ProviderException If something goes wrong.
208: */
209:
210: public Attachment getAttachmentInfo(WikiContext context,
211: String attachmentname, int version)
212: throws ProviderException {
213: if (m_provider == null) {
214: return null;
215: }
216:
217: WikiPage currentPage = null;
218:
219: if (context != null) {
220: currentPage = context.getPage();
221: }
222:
223: //
224: // Figure out the parent page of this attachment. If we can't find it,
225: // we'll assume this refers directly to the attachment.
226: //
227: int cutpt = attachmentname.lastIndexOf('/');
228:
229: if (cutpt != -1) {
230: String parentPage = attachmentname.substring(0, cutpt);
231: parentPage = MarkupParser.cleanLink(parentPage);
232: attachmentname = attachmentname.substring(cutpt + 1);
233:
234: // If we for some reason have an empty parent page name;
235: // this can't be an attachment
236: if (parentPage.length() == 0)
237: return null;
238:
239: currentPage = m_engine.getPage(parentPage);
240:
241: //
242: // Go check for legacy name
243: //
244: // FIXME: This should be resolved using CommandResolver,
245: // not this adhoc way. This also assumes that the
246: // legacy charset is a subset of the full allowed set.
247: if (currentPage == null) {
248: currentPage = m_engine.getPage(MarkupParser
249: .wikifyLink(parentPage));
250: }
251: }
252:
253: //
254: // If the page cannot be determined, we cannot possibly find the
255: // attachments.
256: //
257: if (currentPage == null || currentPage.getName().length() == 0) {
258: return null;
259: }
260:
261: // System.out.println("Seeking info on "+currentPage+"::"+attachmentname);
262:
263: //
264: // Finally, figure out whether this is a real attachment or a generated
265: // attachment.
266: //
267: Attachment att;
268:
269: att = getDynamicAttachment(currentPage.getName() + "/"
270: + attachmentname);
271:
272: if (att == null) {
273: att = m_provider.getAttachmentInfo(currentPage,
274: attachmentname, version);
275: }
276:
277: return att;
278: }
279:
280: /**
281: * Returns the list of attachments associated with a given wiki page.
282: * If there are no attachments, returns an empty Collection.
283: *
284: * @param wikipage The wiki page from which you are seeking attachments for.
285: * @return a valid collection of attachments.
286: * @throws ProviderException If there was something wrong in the backend.
287: */
288:
289: // FIXME: This API should be changed to return a List.
290: public Collection listAttachments(WikiPage wikipage)
291: throws ProviderException {
292: if (m_provider == null) {
293: return new ArrayList();
294: }
295:
296: Collection atts = m_provider.listAttachments(wikipage);
297:
298: //
299: // This is just a sanity check; all of our providers return a Collection.
300: //
301: if (atts instanceof List) {
302: Collections.sort((List) atts);
303: }
304:
305: return atts;
306: }
307:
308: /**
309: * Returns true, if the page has any attachments at all. This is
310: * a convinience method.
311: *
312: *
313: * @param wikipage The wiki page from which you are seeking attachments for.
314: * @return True, if the page has attachments, else false.
315: */
316: public boolean hasAttachments(WikiPage wikipage) {
317: try {
318: return listAttachments(wikipage).size() > 0;
319: } catch (Exception e) {
320: }
321:
322: return false;
323: }
324:
325: /**
326: * Finds a (real) attachment from the repository as a stream.
327: *
328: * @param att Attachment
329: * @return An InputStream to read from. May return null, if
330: * attachments are disabled.
331: * @throws IOException If the stream cannot be opened
332: * @throws ProviderException If the backend fails due to some other reason.
333: */
334: public InputStream getAttachmentStream(Attachment att)
335: throws IOException, ProviderException {
336: return getAttachmentStream(null, att);
337: }
338:
339: /**
340: * Returns an attachment stream using the particular WikiContext. This method
341: * should be used instead of getAttachmentStream(Attachment), since it also allows
342: * the DynamicAttachments to function.
343: *
344: * @param ctx The Wiki Context
345: * @param att The Attachment to find
346: * @return An InputStream. May return null, if attachments are disabled. You must
347: * take care of closing it.
348: * @throws ProviderException If the backend fails due to some reason
349: * @throws IOException If the stream cannot be opened
350: */
351: public InputStream getAttachmentStream(WikiContext ctx,
352: Attachment att) throws ProviderException, IOException {
353: if (m_provider == null) {
354: return null;
355: }
356:
357: if (att instanceof DynamicAttachment) {
358: return ((DynamicAttachment) att).getProvider()
359: .getAttachmentData(ctx, att);
360: }
361:
362: return m_provider.getAttachmentData(att);
363: }
364:
365: private Cache m_dynamicAttachments = new Cache(true, false, false);
366:
367: /**
368: * Stores a dynamic attachment. Unlike storeAttachment(), this just stores
369: * the attachment in the memory.
370: *
371: * @param ctx A WikiContext
372: * @param att An attachment to store
373: */
374: public void storeDynamicAttachment(WikiContext ctx,
375: DynamicAttachment att) {
376: m_dynamicAttachments.putInCache(att.getName(), att);
377: }
378:
379: /**
380: * Finds a DynamicAttachment. Normally, you should just use getAttachmentInfo(),
381: * since that will find also DynamicAttachments.
382: *
383: * @param name The name of the attachment to look for
384: * @return An Attachment, or null.
385: * @see #getAttachmentInfo(String)
386: */
387:
388: public DynamicAttachment getDynamicAttachment(String name) {
389: try {
390: return (DynamicAttachment) m_dynamicAttachments
391: .getFromCache(name);
392: } catch (NeedsRefreshException e) {
393: //
394: // Remove from cache, it has expired.
395: //
396: m_dynamicAttachments.putInCache(name, null);
397:
398: return null;
399: }
400: }
401:
402: /**
403: * Stores an attachment that lives in the given file.
404: * If the attachment did not exist previously, this method
405: * will create it. If it did exist, it stores a new version.
406: *
407: * @param att Attachment to store this under.
408: * @param source A file to read from.
409: *
410: * @throws IOException If writing the attachment failed.
411: * @throws ProviderException If something else went wrong.
412: */
413: public void storeAttachment(Attachment att, File source)
414: throws IOException, ProviderException {
415: FileInputStream in = null;
416:
417: try {
418: in = new FileInputStream(source);
419: storeAttachment(att, in);
420: } finally {
421: if (in != null)
422: in.close();
423: }
424: }
425:
426: /**
427: * Stores an attachment directly from a stream.
428: * If the attachment did not exist previously, this method
429: * will create it. If it did exist, it stores a new version.
430: *
431: * @param att Attachment to store this under.
432: * @param in InputStream from which the attachment contents will be read.
433: *
434: * @throws IOException If writing the attachment failed.
435: * @throws ProviderException If something else went wrong.
436: */
437: public void storeAttachment(Attachment att, InputStream in)
438: throws IOException, ProviderException {
439: if (m_provider == null) {
440: return;
441: }
442:
443: m_provider.putAttachmentData(att, in);
444:
445: m_engine.getReferenceManager().updateReferences(att.getName(),
446: new java.util.Vector());
447:
448: WikiPage parent = new WikiPage(m_engine, att.getParentName());
449: m_engine.updateReferences(parent);
450:
451: m_engine.getSearchManager().reindexPage(att);
452: }
453:
454: /**
455: * Returns a list of versions of the attachment.
456: *
457: * @param attachmentName A fully qualified name of the attachment.
458: *
459: * @return A list of Attachments. May return null, if attachments are
460: * disabled.
461: * @throws ProviderException If the provider fails for some reason.
462: */
463: public List getVersionHistory(String attachmentName)
464: throws ProviderException {
465: if (m_provider == null) {
466: return null;
467: }
468:
469: Attachment att = getAttachmentInfo((WikiContext) null,
470: attachmentName);
471:
472: if (att != null) {
473: return m_provider.getVersionHistory(att);
474: }
475:
476: return null;
477: }
478:
479: /**
480: * Returns a collection of Attachments, containing each and every attachment
481: * that is in this Wiki.
482: *
483: * @return A collection of attachments. If attachments are disabled, will
484: * return an empty collection.
485: * @throws ProviderException If something went wrong with the backend
486: */
487: public Collection getAllAttachments() throws ProviderException {
488: if (attachmentsEnabled()) {
489: return m_provider.listAllChanged(new Date(0L));
490: }
491:
492: return new ArrayList();
493: }
494:
495: /**
496: * Returns the current attachment provider.
497: *
498: * @return The current provider. May be null, if attachments are disabled.
499: */
500: public WikiAttachmentProvider getCurrentProvider() {
501: return m_provider;
502: }
503:
504: /**
505: * Deletes the given attachment version.
506: *
507: * @param att The attachment to delete
508: * @throws ProviderException If something goes wrong with the backend.
509: */
510: public void deleteVersion(Attachment att) throws ProviderException {
511: if (m_provider == null)
512: return;
513:
514: m_provider.deleteVersion(att);
515: }
516:
517: /**
518: * Deletes all versions of the given attachment.
519: * @param att The Attachment to delete.
520: * @throws ProviderException if something goes wrong with the backend.
521: */
522: // FIXME: Should also use events!
523: public void deleteAttachment(Attachment att)
524: throws ProviderException {
525: if (m_provider == null)
526: return;
527:
528: m_provider.deleteAttachment(att);
529:
530: m_engine.getSearchManager().pageRemoved(att);
531:
532: m_engine.getReferenceManager().clearPageEntries(att.getName());
533:
534: }
535: }
|