    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}