001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.providers; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.*; 024 025import net.sf.ehcache.Cache; 026import net.sf.ehcache.CacheManager; 027import net.sf.ehcache.Element; 028 029import org.apache.log4j.Logger; 030import org.apache.wiki.*; 031import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 032import org.apache.wiki.api.exceptions.ProviderException; 033import org.apache.wiki.attachment.Attachment; 034import org.apache.wiki.attachment.AttachmentManager; 035import org.apache.wiki.search.QueryItem; 036import org.apache.wiki.util.ClassUtil; 037import org.apache.wiki.util.TextUtil; 038 039/** 040 * Provides a caching attachment provider. This class rests on top of a 041 * real provider class and provides a cache to speed things up. Only the 042 * Attachment objects are cached; the actual attachment contents are 043 * fetched always from the provider. 044 * 045 * @since 2.1.64. 046 */ 047 048// EntryRefreshPolicy for that. 049public class CachingAttachmentProvider 050 implements WikiAttachmentProvider 051{ 052 private static final Logger log = Logger.getLogger(CachingAttachmentProvider.class); 053 054 private WikiAttachmentProvider m_provider; 055 056 private CacheManager m_cacheManager = CacheManager.getInstance(); 057 058 /** Default cache capacity for now. */ 059 public static final int m_capacity = 1000; 060 061 /** 062 * The cache contains Collection objects which contain Attachment objects. 063 * The key is the parent wiki page name (String). 064 */ 065 private Cache m_cache; 066 /** Name of the attachment cache. */ 067 public static final String ATTCOLLCACHE_NAME = "jspwiki.attachmentCollectionsCache"; 068 069 /** 070 * This cache contains Attachment objects and is keyed by attachment name. 071 * This provides for quickly giving recently changed attachments (for the RecentChanges plugin) 072 */ 073 private Cache m_attCache; 074 /** Name of the attachment cache. */ 075 public static final String ATTCACHE_NAME = "jspwiki.attachmentsCache"; 076 077 private long m_cacheMisses = 0; 078 private long m_cacheHits = 0; 079 080 /** The extension to append to directory names to denote an attachment directory. */ 081 public static final String DIR_EXTENSION = "-att"; 082 083 /** Property that supplies the directory used to store attachments. */ 084 public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir"; 085 086 private boolean m_gotall = false; 087 088 /** 089 * {@inheritDoc} 090 */ 091 public void initialize( WikiEngine engine, Properties properties ) 092 throws NoRequiredPropertyException, 093 IOException 094 { 095 log.info("Initing CachingAttachmentProvider"); 096 097 String attCollCacheName = engine.getApplicationName() + "." + ATTCOLLCACHE_NAME; 098 if (m_cacheManager.cacheExists(attCollCacheName)) { 099 m_cache = m_cacheManager.getCache(attCollCacheName); 100 } else { 101 m_cache = new Cache(attCollCacheName, m_capacity, false, false, 0, 0); 102 m_cacheManager.addCache(m_cache); 103 } 104 105 // 106 // cache for the individual Attachment objects, attachment name is key, the Attachment object is the cached object 107 // 108 String attCacheName = engine.getApplicationName() + "." + ATTCACHE_NAME; 109 if (m_cacheManager.cacheExists(attCacheName)) { 110 m_attCache = m_cacheManager.getCache(attCacheName); 111 } else { 112 m_attCache = new Cache(attCacheName, m_capacity, false, false, 0, 0); 113 m_cacheManager.addCache(m_attCache); 114 } 115 // 116 // Find and initialize real provider. 117 // 118 String classname = TextUtil.getRequiredProperty( properties, AttachmentManager.PROP_PROVIDER ); 119 120 try 121 { 122 Class<?> providerclass = ClassUtil.findClass( "org.apache.wiki.providers", classname); 123 124 m_provider = (WikiAttachmentProvider)providerclass.newInstance(); 125 126 log.debug("Initializing real provider class "+m_provider); 127 m_provider.initialize( engine, properties ); 128 } 129 catch( ClassNotFoundException e ) 130 { 131 log.error("Unable to locate provider class "+classname,e); 132 throw new IllegalArgumentException("no provider class", e); 133 } 134 catch( InstantiationException e ) 135 { 136 log.error("Unable to create provider class "+classname,e); 137 throw new IllegalArgumentException("faulty provider class", e); 138 } 139 catch( IllegalAccessException e ) 140 { 141 log.error("Illegal access to provider class "+classname,e); 142 throw new IllegalArgumentException("illegal provider class", e); 143 } 144 145 } 146 147 /** 148 * {@inheritDoc} 149 */ 150 public void putAttachmentData( Attachment att, InputStream data ) 151 throws ProviderException, IOException { 152 m_provider.putAttachmentData( att, data ); 153 154 m_cache.remove(att.getParentName()); 155 att.setLastModified(new Date()); 156 m_attCache.put(new Element(att.getName(), att)); 157 } 158 159 /** 160 * {@inheritDoc} 161 */ 162 public InputStream getAttachmentData( Attachment att ) 163 throws ProviderException, IOException { 164 return m_provider.getAttachmentData( att ); 165 } 166 167 /** 168 * {@inheritDoc} 169 */ 170 public Collection listAttachments(WikiPage page) throws ProviderException { 171 log.debug("Listing attachments for " + page); 172 Collection<Attachment> c = null; 173 Element element = m_cache.get(page.getName()); 174 175 if (element != null) { 176 c = (Collection<Attachment>) element.getObjectValue(); 177 log.debug("LIST from cache, " + page.getName() + ", size=" + c.size()); 178 return cloneCollection(c); 179 } 180 181 log.debug("list NOT in cache, " + page.getName()); 182 183 return refresh(page); 184 } 185 186 private <T> Collection<T> cloneCollection( Collection<T> c ) 187 { 188 ArrayList<T> list = new ArrayList<T>(); 189 190 list.addAll( c ); 191 192 return list; 193 } 194 195 /** 196 * {@inheritDoc} 197 */ 198 public Collection findAttachments( QueryItem[] query ) 199 { 200 return m_provider.findAttachments( query ); 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 public List listAllChanged(Date timestamp) throws ProviderException { 207 List all = null; 208 // 209 // we do a one-time build up of the cache, after this the cache is updated for every attachment add/delete 210 if (m_gotall == false) { 211 all = m_provider.listAllChanged(timestamp); 212 213 // Put all pages in the cache : 214 215 synchronized (this) { 216 for (Iterator i = all.iterator(); i.hasNext(); ) { 217 Attachment att = (Attachment) i.next(); 218 m_attCache.put(new Element(att.getName(), att)); 219 } 220 m_gotall = true; 221 } 222 } else { 223 List<String> keys = m_attCache.getKeysWithExpiryCheck(); 224 all = new ArrayList(); 225 for (String key : keys) { 226 Element element = m_attCache.get(key); 227 Object cachedAttachment = element.getObjectValue(); 228 if (cachedAttachment != null) { 229 all.add((Attachment) cachedAttachment); 230 } 231 } 232 } 233 234 return all; 235 } 236 237 /** 238 * Simply goes through the collection and attempts to locate the 239 * given attachment of that name. 240 * 241 * @return null, if no such attachment was in this collection. 242 */ 243 private Attachment findAttachmentFromCollection( Collection< Attachment > c, String name ) { 244 for( Attachment att : c ) { 245 if( name.equals( att.getFileName() ) ) { 246 return att; 247 } 248 } 249 250 return null; 251 } 252 253 /** 254 * Refreshes the cache content and updates counters. 255 * 256 * @return The newly fetched object from the provider. 257 */ 258 private Collection<Attachment> refresh( WikiPage page ) throws ProviderException 259 { 260 Collection<Attachment> c = m_provider.listAttachments( page ); 261 m_cache.put(new Element(page.getName(), c)); 262 263 return c; 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 public Attachment getAttachmentInfo(WikiPage page, String name, int version) throws ProviderException { 270 if (log.isDebugEnabled()) { 271 log.debug("Getting attachments for " + page + ", name=" + name + ", version=" + version); 272 } 273 274 // 275 // We don't cache previous versions 276 // 277 if (version != WikiProvider.LATEST_VERSION) { 278 log.debug("...we don't cache old versions"); 279 return m_provider.getAttachmentInfo(page, name, version); 280 } 281 282 Collection<Attachment> c = null; 283 Element element = m_cache.get(page.getName()); 284 285 if (element == null) { 286 log.debug(page.getName() + " wasn't in the cache"); 287 c = refresh(page); 288 289 if (c == null) return null; // No such attachment 290 } else { 291 log.debug(page.getName() + " FOUND in the cache"); 292 c = (Collection<Attachment>) element.getObjectValue(); 293 } 294 295 return findAttachmentFromCollection(c, name); 296 } 297 298 /** 299 * {@inheritDoc} 300 */ 301 public List getVersionHistory( Attachment att ) 302 { 303 return m_provider.getVersionHistory( att ); 304 } 305 306 /** 307 * {@inheritDoc} 308 */ 309 public void deleteVersion( Attachment att ) throws ProviderException 310 { 311 // This isn't strictly speaking correct, but it does not really matter 312 m_cache.remove(att.getParentName()); 313 m_provider.deleteVersion( att ); 314 } 315 316 /** 317 * {@inheritDoc} 318 */ 319 public void deleteAttachment( Attachment att ) throws ProviderException 320 { 321 m_cache.remove(att.getParentName()); 322 m_attCache.remove(att.getName()); 323 m_provider.deleteAttachment( att ); 324 } 325 326 327 /** 328 * Gets the provider class name, and cache statistics (misscount and,hitcount of the attachment cache). 329 * 330 * @return A plain string with all the above mentioned values. 331 */ 332 public synchronized String getProviderInfo() 333 { 334 return "Real provider: "+m_provider.getClass().getName()+ 335 ". Cache misses: "+m_cacheMisses+ 336 ". Cache hits: "+m_cacheHits; 337 } 338 339 /** 340 * Returns the WikiAttachmentProvider that this caching provider delegates to. 341 * 342 * @return The real provider underneath this one. 343 */ 344 public WikiAttachmentProvider getRealProvider() 345 { 346 return m_provider; 347 } 348 349 /** 350 * {@inheritDoc} 351 */ 352 public void moveAttachmentsForPage( String oldParent, String newParent ) throws ProviderException 353 { 354 m_provider.moveAttachmentsForPage(oldParent, newParent); 355 m_cache.remove(newParent); 356 m_cache.remove(oldParent); 357 358 // 359 // This is a kludge to make sure that the pages are removed 360 // from the other cache as well. 361 // 362 String checkName = oldParent + "/"; 363 364 Collection< String > names = m_cache.getKeysWithExpiryCheck(); 365 for( String name : names ) 366 { 367 if( name.startsWith( checkName ) ) 368 { 369 m_attCache.remove(name); 370 } 371 } 372 } 373 374}