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