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