001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2005 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.providers;
021:
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.util.*;
025:
026: import org.apache.log4j.Logger;
027:
028: import com.ecyrd.jspwiki.*;
029: import com.ecyrd.jspwiki.attachment.Attachment;
030: import com.ecyrd.jspwiki.attachment.AttachmentManager;
031: import com.ecyrd.jspwiki.util.ClassUtil;
032: import com.opensymphony.oscache.base.Cache;
033: import com.opensymphony.oscache.base.NeedsRefreshException;
034:
035: /**
036: * Provides a caching attachment provider. This class rests on top of a
037: * real provider class and provides a cache to speed things up. Only the
038: * Attachment objects are cached; the actual attachment contents are
039: * fetched always from the provider.
040: *
041: * @author Janne Jalkanen
042: * @since 2.1.64.
043: */
044:
045: // FIXME: Do we need to clear the cache entry if we get an NRE and the attachment is not there?
046: // FIXME: We probably clear the cache a bit too aggressively in places.
047: // FIXME: Does not yet react well to external cache changes. Should really use custom
048: // EntryRefreshPolicy for that.
049: public class CachingAttachmentProvider implements
050: WikiAttachmentProvider {
051: private static final Logger log = Logger
052: .getLogger(CachingAttachmentProvider.class);
053:
054: private WikiAttachmentProvider m_provider;
055:
056: /**
057: * The cache contains Collection objects which contain Attachment objects.
058: * The key is the parent wiki page name (String).
059: */
060: private Cache m_cache;
061:
062: private long m_cacheMisses = 0;
063: private long m_cacheHits = 0;
064:
065: /** The extension to append to directory names to denote an attachment directory. */
066: public static final String DIR_EXTENSION = "-att";
067:
068: /** Property that supplies the directory used to store attachments. */
069: public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";
070:
071: // FIXME: Make settable.
072: private int m_refreshPeriod = 60 * 10; // 10 minutes at the moment
073:
074: /**
075: * {@inheritDoc}
076: */
077: public void initialize(WikiEngine engine, Properties properties)
078: throws NoRequiredPropertyException, IOException {
079: log.debug("Initing CachingAttachmentProvider");
080:
081: //
082: // Construct an unlimited cache.
083: //
084: m_cache = new Cache(true, false, true);
085:
086: //
087: // Find and initialize real provider.
088: //
089: String classname = WikiEngine.getRequiredProperty(properties,
090: AttachmentManager.PROP_PROVIDER);
091:
092: try {
093: Class providerclass = ClassUtil.findClass(
094: "com.ecyrd.jspwiki.providers", classname);
095:
096: m_provider = (WikiAttachmentProvider) providerclass
097: .newInstance();
098:
099: log.debug("Initializing real provider class " + m_provider);
100: m_provider.initialize(engine, properties);
101: } catch (ClassNotFoundException e) {
102: log
103: .error("Unable to locate provider class "
104: + classname, e);
105: throw new IllegalArgumentException("no provider class");
106: } catch (InstantiationException e) {
107: log
108: .error("Unable to create provider class "
109: + classname, e);
110: throw new IllegalArgumentException("faulty provider class");
111: } catch (IllegalAccessException e) {
112: log.error("Illegal access to provider class " + classname,
113: e);
114: throw new IllegalArgumentException("illegal provider class");
115: }
116:
117: }
118:
119: /**
120: * {@inheritDoc}
121: */
122: public void putAttachmentData(Attachment att, InputStream data)
123: throws ProviderException, IOException {
124: m_provider.putAttachmentData(att, data);
125:
126: m_cache.flushEntry(att.getParentName());
127: }
128:
129: /**
130: * {@inheritDoc}
131: */
132: public InputStream getAttachmentData(Attachment att)
133: throws ProviderException, IOException {
134: return m_provider.getAttachmentData(att);
135: }
136:
137: /**
138: * {@inheritDoc}
139: */
140: public Collection listAttachments(WikiPage page)
141: throws ProviderException {
142: log.debug("Listing attachments for " + page);
143: try {
144: Collection c = (Collection) m_cache.getFromCache(page
145: .getName(), m_refreshPeriod);
146:
147: if (c != null) {
148: log.debug("LIST from cache, " + page.getName()
149: + ", size=" + c.size());
150: m_cacheHits++;
151: return cloneCollection(c);
152: }
153:
154: log.debug("list NOT in cache, " + page.getName());
155:
156: refresh(page);
157: } catch (NeedsRefreshException nre) {
158: try {
159: Collection c = refresh(page);
160:
161: return cloneCollection(c);
162: } catch (Exception ex) {
163: // Is a catch-all, because cache will get confused if
164: // we let this one go.
165: log.warn("Provider failed, returning cached content",
166: ex);
167:
168: m_cache.cancelUpdate(page.getName());
169:
170: return (Collection) nre.getCacheContent();
171: }
172: }
173:
174: return new ArrayList();
175: }
176:
177: private Collection cloneCollection(Collection c) {
178: ArrayList list = new ArrayList();
179:
180: list.addAll(c);
181:
182: return list;
183: }
184:
185: /**
186: * {@inheritDoc}
187: */
188: public Collection findAttachments(QueryItem[] query) {
189: return m_provider.findAttachments(query);
190: }
191:
192: /**
193: * {@inheritDoc}
194: */
195: public List listAllChanged(Date timestamp) throws ProviderException {
196: // FIXME: Should cache
197: return m_provider.listAllChanged(timestamp);
198: }
199:
200: /**
201: * Simply goes through the collection and attempts to locate the
202: * given attachment of that name.
203: *
204: * @return null, if no such attachment was in this collection.
205: */
206: private Attachment findAttachmentFromCollection(Collection c,
207: String name) {
208: for (Iterator i = c.iterator(); i.hasNext();) {
209: Attachment att = (Attachment) i.next();
210:
211: if (name.equals(att.getFileName())) {
212: return att;
213: }
214: }
215:
216: return null;
217: }
218:
219: /**
220: * Refreshes the cache content and updates counters.
221: *
222: * @return The newly fetched object from the provider.
223: */
224: private final Collection refresh(WikiPage page)
225: throws ProviderException {
226: m_cacheMisses++;
227: Collection c = m_provider.listAttachments(page);
228: m_cache.putInCache(page.getName(), c);
229:
230: return c;
231: }
232:
233: /**
234: * {@inheritDoc}
235: */
236: public Attachment getAttachmentInfo(WikiPage page, String name,
237: int version) throws ProviderException {
238: if (log.isDebugEnabled()) {
239: log.debug("Getting attachments for " + page + ", name="
240: + name + ", version=" + version);
241: }
242:
243: //
244: // We don't cache previous versions
245: //
246: if (version != WikiProvider.LATEST_VERSION) {
247: log.debug("...we don't cache old versions");
248: return m_provider.getAttachmentInfo(page, name, version);
249: }
250:
251: try {
252: Collection c = (Collection) m_cache.getFromCache(page
253: .getName(), m_refreshPeriod);
254:
255: if (c == null) {
256: log.debug("...wasn't in the cache");
257: c = refresh(page);
258:
259: if (c == null)
260: return null; // No such attachment
261: } else {
262: log.debug("...FOUND in the cache");
263: m_cacheHits++;
264: }
265:
266: return findAttachmentFromCollection(c, name);
267:
268: } catch (NeedsRefreshException nre) {
269: log.debug("...needs refresh");
270: Collection c = null;
271:
272: try {
273: c = refresh(page);
274: } catch (Exception ex) {
275: log.warn("Provider failed, returning cached content",
276: ex);
277:
278: m_cache.cancelUpdate(page.getName());
279: c = (Collection) nre.getCacheContent();
280: }
281:
282: if (c != null) {
283: return findAttachmentFromCollection(c, name);
284: }
285: }
286:
287: return null;
288: }
289:
290: /**
291: * {@inheritDoc}
292: */
293: public List getVersionHistory(Attachment att) {
294: return m_provider.getVersionHistory(att);
295: }
296:
297: /**
298: * {@inheritDoc}
299: */
300: public void deleteVersion(Attachment att) throws ProviderException {
301: // This isn't strictly speaking correct, but it does not really matter
302: m_cache.putInCache(att.getParentName(), null);
303: m_provider.deleteVersion(att);
304: }
305:
306: /**
307: * {@inheritDoc}
308: */
309: public void deleteAttachment(Attachment att)
310: throws ProviderException {
311: m_cache.putInCache(att.getParentName(), null);
312: m_provider.deleteAttachment(att);
313: }
314:
315: /**
316: * {@inheritDoc}
317: */
318: public synchronized String getProviderInfo() {
319: return "Real provider: " + m_provider.getClass().getName()
320: + ". Cache misses: " + m_cacheMisses
321: + ". Cache hits: " + m_cacheHits;
322: }
323:
324: /**
325: * {@inheritDoc}
326: */
327: public WikiAttachmentProvider getRealProvider() {
328: return m_provider;
329: }
330:
331: /**
332: * {@inheritDoc}
333: */
334: public void moveAttachmentsForPage(String oldParent,
335: String newParent) throws ProviderException {
336: m_provider.moveAttachmentsForPage(oldParent, newParent);
337: m_cache.putInCache(newParent, null); // FIXME
338: m_cache.putInCache(oldParent, null);
339: }
340: }
|