001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.request.resource.caching.version;
018
019import java.io.Serializable;
020import java.util.Collections;
021import java.util.Map;
022import java.util.regex.Pattern;
023
024import org.apache.wicket.request.resource.caching.IStaticCacheableResource;
025import org.apache.wicket.util.collections.MostRecentlyUsedMap;
026import org.apache.wicket.util.lang.Args;
027
028/**
029 * Caches the results of a delegating {@link IResourceVersion} instance
030 * in a member variable. The cache will be valid for the lifetime of 
031 * this instance. It will expire the oldest entries if the maximum number 
032 * of entries is exceeded.
033 * 
034 * @author Peter Ertl
035 * 
036 * @since 1.5
037 */
038public class CachingResourceVersion implements IResourceVersion
039{
040        /**
041         * default maximum entries in cache
042         */
043        private static final int DEFAULT_MAX_CACHE_ENTRIES = 5000;
044
045        /**
046         * null value replacement holder for storing <code>null</code> in the map 
047         */
048        private static final String NULL_VALUE = "null";
049
050        /**
051         * delegating resource version provider
052         */
053        private final IResourceVersion delegate;
054
055        /**
056         * cache for resource versions
057         */
058        private final Map<Serializable, String> cache;
059
060        /**
061         * create version cache
062         * <p/>
063         * the cache will accept up to {@value #DEFAULT_MAX_CACHE_ENTRIES} before 
064         * evicting the oldest entries.
065         * 
066         * @param delegate
067         *           delegating resource version provider
068         */
069        public CachingResourceVersion(IResourceVersion delegate)
070        {
071                this(delegate, DEFAULT_MAX_CACHE_ENTRIES);
072        }
073
074        /**
075         * create version cache
076         * <p/>
077         * the cache will accept a maximum number of entries specified
078         * by <code>maxEntries</code> before evicting the oldest entries.
079         * 
080         * @param delegate
081         *          resource version provider
082         * @param maxEntries
083         *          maximum number of cache entries
084         */        
085        public CachingResourceVersion(IResourceVersion delegate, int maxEntries)
086        {
087                if (maxEntries < 1)
088                {
089                        throw new IllegalArgumentException("maxEntries must be greater than zero");
090                }
091
092                this.delegate = Args.notNull(delegate, "delegate");
093                this.cache = Collections.synchronizedMap(
094                        new MostRecentlyUsedMap<Serializable, String>(maxEntries));
095        }
096
097        @Override
098        public String getVersion(IStaticCacheableResource resource)
099        {
100                // get unique cache key for resource reference
101                final Serializable key = resource.getCacheKey();
102
103                // if key can not be determined do not cache
104                if(key == null)
105                {
106                        return null;
107                }
108                
109                // lookup version in cache
110                String version = cache.get(key);
111
112                // if not found
113                if (version == null)
114                {
115                        // get version from delegate
116                        version = delegate.getVersion(resource);
117
118                        // replace null values with holder
119                        if (version == null)
120                        {
121                                version = NULL_VALUE;
122                        }
123                        // update cache
124                        cache.put(key, version);
125                }
126
127                //noinspection StringEquality
128                if (version == NULL_VALUE)
129                {
130                        // replace holder with null value
131                        return null;
132                }
133                
134                // return version string
135                return version;
136        }
137
138        @Override
139        public Pattern getVersionPattern() {
140                return delegate.getVersionPattern();
141        }
142
143        /**
144         * remove cacheable resource from cache
145         * 
146         * @param resource 
147         *           cacheable resource
148         */
149        public void invalidate(IStaticCacheableResource resource)
150        {   
151                // get cache key for resource reference
152                final Serializable key = Args.notNull(resource, "resource").getCacheKey();
153
154                // if key is available purge cache entry
155                if(key != null)
156                {
157                        cache.remove(key);
158                }
159        }
160
161  public void invalidateAll() {
162    cache.clear();
163  }
164}