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.cache; 020 021import net.sf.ehcache.Cache; 022import net.sf.ehcache.CacheManager; 023import net.sf.ehcache.Ehcache; 024import net.sf.ehcache.Element; 025import net.sf.ehcache.event.CacheEventListenerAdapter; 026import org.apache.logging.log4j.LogManager; 027import org.apache.logging.log4j.Logger; 028import org.apache.wiki.api.core.Engine; 029import org.apache.wiki.api.engine.Initializable; 030import org.apache.wiki.api.exceptions.WikiException; 031import org.apache.wiki.util.CheckedSupplier; 032import org.apache.wiki.util.TextUtil; 033 034import java.io.Serializable; 035import java.net.URL; 036import java.util.Collections; 037import java.util.List; 038import java.util.Map; 039import java.util.Properties; 040import java.util.concurrent.ConcurrentHashMap; 041import java.util.concurrent.atomic.AtomicBoolean; 042 043 044/** 045 * Ehcache-based {@link CachingManager}. 046 */ 047public class EhcacheCachingManager implements CachingManager, Initializable { 048 049 private static final Logger LOG = LogManager.getLogger( EhcacheCachingManager.class ); 050 private static final int DEFAULT_CACHE_SIZE = 1_000; 051 private static final int DEFAULT_CACHE_EXPIRY_PERIOD = 24*60*60; 052 053 final Map< String, Cache > cacheMap = new ConcurrentHashMap<>(); 054 final Map< String, CacheInfo > cacheStats = new ConcurrentHashMap<>(); 055 CacheManager cacheManager; 056 057 /** {@inheritDoc} */ 058 @Override 059 public void shutdown() { 060 if(!cacheMap.isEmpty()) { 061 CacheManager.getInstance().shutdown(); 062 cacheMap.clear(); 063 cacheStats.clear(); 064 } 065 } 066 067 /** {@inheritDoc} */ 068 @Override 069 public void initialize( final Engine engine, final Properties props ) throws WikiException { 070 final String cacheEnabled = TextUtil.getStringProperty( props, PROP_CACHE_ENABLE, PROP_USECACHE_DEPRECATED, "true" ); 071 final boolean useCache = "true".equalsIgnoreCase( cacheEnabled ); 072 final String confLocation = "/" + TextUtil.getStringProperty( props, PROP_CACHE_CONF_FILE, "ehcache-jspwiki.xml" ); 073 if( useCache ) { 074 final URL location = this.getClass().getResource( confLocation ); 075 LOG.info( "Reading ehcache configuration file from classpath on /{}", location ); 076 cacheManager = CacheManager.create( location ); 077 registerCache( CACHE_ATTACHMENTS ); 078 registerCache( CACHE_ATTACHMENTS_COLLECTION ); 079 registerCache( CACHE_ATTACHMENTS_DYNAMIC ); 080 registerCache( CACHE_DOCUMENTS ); 081 registerCache( CACHE_PAGES ); 082 registerCache( CACHE_PAGES_HISTORY ); 083 registerCache( CACHE_PAGES_TEXT ); 084 } 085 } 086 087 void registerCache( final String cacheName ) { 088 final Cache cache; 089 if( cacheManager.cacheExists( cacheName ) ) { 090 cache = cacheManager.getCache( cacheName ); 091 } else { 092 LOG.info( "cache with name {} not found in ehcache configuration file, creating it with defaults.", cacheName ); 093 cache = new Cache( cacheName, DEFAULT_CACHE_SIZE, false, false, DEFAULT_CACHE_EXPIRY_PERIOD, DEFAULT_CACHE_EXPIRY_PERIOD ); 094 cacheManager.addCache( cache ); 095 } 096 cacheMap.put( cacheName, cache ); 097 cacheStats.put( cacheName, new CacheInfo( cacheName, cache.getCacheConfiguration().getMaxEntriesLocalHeap() ) ); 098 } 099 100 /** {@inheritDoc} */ 101 @Override 102 public boolean enabled( final String cacheName ) { 103 return cacheMap.get( cacheName ) != null; 104 } 105 106 /** {@inheritDoc} */ 107 @Override 108 public CacheInfo info( final String cacheName ) { 109 if( enabled( cacheName ) ) { 110 return cacheStats.get( cacheName ); 111 } 112 return null; 113 } 114 115 /** 116 * {@inheritDoc} 117 */ 118 @Override 119 @SuppressWarnings( "unchecked" ) 120 public List< String > keys( final String cacheName ) { 121 if( enabled( cacheName ) ) { 122 return cacheMap.get( cacheName ).getKeysWithExpiryCheck(); 123 } 124 return Collections.emptyList(); 125 } 126 127 /** {@inheritDoc} */ 128 @Override 129 @SuppressWarnings( "unchecked" ) 130 public < T, E extends Exception > T get( final String cacheName, final Serializable key, final CheckedSupplier< T, E > supplier ) throws E { 131 if( keyAndCacheAreNotNull( cacheName, key ) ) { 132 final Element element = cacheMap.get( cacheName ).get( key ); 133 if( element != null ) { 134 cacheStats.get( cacheName ).hit(); 135 return ( T )element.getObjectValue(); 136 } else { 137 // element doesn't exist in cache, try to retrieve from the cached service instead. 138 final T value = supplier.get(); 139 if( value != null ) { 140 cacheStats.get( cacheName ).miss(); 141 cacheMap.get( cacheName ).put( new Element( key, value ) ); 142 } 143 return value; 144 } 145 } 146 return null; 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public void put( final String cacheName, final Serializable key, final Object val ) { 152 if( keyAndCacheAreNotNull( cacheName, key ) ) { 153 cacheMap.get( cacheName ).put( new Element( key, val ) ); 154 } 155 } 156 157 /** {@inheritDoc} */ 158 @Override 159 public void remove( final String cacheName, final Serializable key ) { 160 if( keyAndCacheAreNotNull( cacheName, key ) ) { 161 cacheMap.get( cacheName ).remove( key ); 162 } 163 } 164 165 /** {@inheritDoc} */ 166 @Override 167 public boolean registerListener( final String cacheName, final String listener, final Object... args ) { 168 if( enabled( cacheName ) && "expired".equals( listener ) ) { 169 final AtomicBoolean allRequested = ( AtomicBoolean )args[0]; 170 final CacheEventListenerAdapter expiredCacheListenerAdapter = new CacheEventListenerAdapter() { 171 @Override 172 public void notifyElementExpired( final Ehcache cache, final Element element ) { 173 allRequested.set( false ); // signal that the cache no longer contains all elements... 174 } 175 }; 176 return cacheMap.get( cacheName ).getCacheEventNotificationService().registerListener( expiredCacheListenerAdapter ); 177 } 178 return false; 179 } 180 181 boolean keyAndCacheAreNotNull( final String cacheName, final Serializable key ) { 182 return enabled( cacheName ) && key != null; 183 } 184 185}