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 */ 019 package org.apache.wiki.providers; 020 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.util.*; 024 025 import net.sf.ehcache.Cache; 026 import net.sf.ehcache.CacheManager; 027 import net.sf.ehcache.Element; 028 029 import org.apache.log4j.Logger; 030 import org.apache.wiki.*; 031 import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 032 import org.apache.wiki.api.exceptions.ProviderException; 033 import org.apache.wiki.attachment.Attachment; 034 import org.apache.wiki.attachment.AttachmentManager; 035 import org.apache.wiki.search.QueryItem; 036 import org.apache.wiki.util.ClassUtil; 037 import 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. 049 public 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 c, String name ) 244 { 245 for( Iterator i = c.iterator(); i.hasNext(); ) 246 { 247 Attachment att = (Attachment) i.next(); 248 249 if( name.equals( att.getFileName() ) ) 250 { 251 return att; 252 } 253 } 254 255 return null; 256 } 257 258 /** 259 * Refreshes the cache content and updates counters. 260 * 261 * @return The newly fetched object from the provider. 262 */ 263 private Collection<Attachment> refresh( WikiPage page ) throws ProviderException 264 { 265 Collection<Attachment> c = m_provider.listAttachments( page ); 266 m_cache.put(new Element(page.getName(), c)); 267 268 return c; 269 } 270 271 /** 272 * {@inheritDoc} 273 */ 274 public Attachment getAttachmentInfo(WikiPage page, String name, int version) throws ProviderException { 275 if (log.isDebugEnabled()) { 276 log.debug("Getting attachments for " + page + ", name=" + name + ", version=" + version); 277 } 278 279 // 280 // We don't cache previous versions 281 // 282 if (version != WikiProvider.LATEST_VERSION) { 283 log.debug("...we don't cache old versions"); 284 return m_provider.getAttachmentInfo(page, name, version); 285 } 286 287 Collection<Attachment> c = null; 288 Element element = m_cache.get(page.getName()); 289 290 if (element == null) { 291 log.debug(page.getName() + " wasn't in the cache"); 292 c = refresh(page); 293 294 if (c == null) return null; // No such attachment 295 } else { 296 log.debug(page.getName() + " FOUND in the cache"); 297 c = (Collection<Attachment>) element.getObjectValue(); 298 } 299 300 return findAttachmentFromCollection(c, name); 301 } 302 303 /** 304 * {@inheritDoc} 305 */ 306 public List getVersionHistory( Attachment att ) 307 { 308 return m_provider.getVersionHistory( att ); 309 } 310 311 /** 312 * {@inheritDoc} 313 */ 314 public void deleteVersion( Attachment att ) throws ProviderException 315 { 316 // This isn't strictly speaking correct, but it does not really matter 317 m_cache.remove(att.getParentName()); 318 m_provider.deleteVersion( att ); 319 } 320 321 /** 322 * {@inheritDoc} 323 */ 324 public void deleteAttachment( Attachment att ) throws ProviderException 325 { 326 m_cache.remove(att.getParentName()); 327 m_attCache.remove(att.getName()); 328 m_provider.deleteAttachment( att ); 329 } 330 331 332 /** 333 * Gets the provider class name, and cache statistics (misscount and,hitcount of the attachment cache). 334 * 335 * @return A plain string with all the above mentioned values. 336 */ 337 public synchronized String getProviderInfo() 338 { 339 return "Real provider: "+m_provider.getClass().getName()+ 340 ". Cache misses: "+m_cacheMisses+ 341 ". Cache hits: "+m_cacheHits; 342 } 343 344 /** 345 * Returns the WikiAttachmentProvider that this caching provider delegates to. 346 * 347 * @return The real provider underneath this one. 348 */ 349 public WikiAttachmentProvider getRealProvider() 350 { 351 return m_provider; 352 } 353 354 /** 355 * {@inheritDoc} 356 */ 357 public void moveAttachmentsForPage( String oldParent, String newParent ) throws ProviderException 358 { 359 m_provider.moveAttachmentsForPage(oldParent, newParent); 360 m_cache.remove(newParent); 361 m_cache.remove(oldParent); 362 363 // 364 // This is a kludge to make sure that the pages are removed 365 // from the other cache as well. 366 // 367 String checkName = oldParent + "/"; 368 369 Collection<String> names = m_cache.getKeysWithExpiryCheck(); 370 for( String name : names ) 371 { 372 if( name.startsWith( checkName ) ) 373 { 374 m_attCache.remove(name); 375 } 376 } 377 } 378 379 }