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.commons.jcs.jcache;
020
021import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
022import org.apache.commons.jcs.engine.behavior.IElementAttributes;
023import org.apache.commons.jcs.engine.control.CompositeCache;
024import org.apache.commons.jcs.engine.control.CompositeCacheConfigurator;
025import org.apache.commons.jcs.engine.control.CompositeCacheManager;
026import org.apache.commons.jcs.jcache.lang.Subsitutor;
027import org.apache.commons.jcs.jcache.proxy.ClassLoaderAwareCache;
028
029import javax.cache.Cache;
030import javax.cache.CacheManager;
031import javax.cache.configuration.Configuration;
032import javax.cache.spi.CachingProvider;
033import java.io.ByteArrayInputStream;
034import java.io.IOException;
035import java.io.InputStream;
036import java.net.URI;
037import java.net.URL;
038import java.util.Enumeration;
039import java.util.Map;
040import java.util.Properties;
041import java.util.concurrent.ConcurrentHashMap;
042import java.util.concurrent.ConcurrentMap;
043
044import static org.apache.commons.jcs.jcache.Asserts.assertNotNull;
045
046public class JCSCachingManager implements CacheManager
047{
048    private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE;
049    private static final String DEFAULT_CONFIG =
050        "jcs.default=DC\n" +
051        "jcs.default.cacheattributes=org.apache.commons.jcs.engine.CompositeCacheAttributes\n" +
052        "jcs.default.cacheattributes.MaxObjects=200001\n" +
053        "jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs.engine.memory.lru.LRUMemoryCache\n" +
054        "jcs.default.cacheattributes.UseMemoryShrinker=true\n" +
055        "jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" +
056        "jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" +
057        "jcs.default.elementattributes=org.apache.commons.jcs.engine.ElementAttributes\n" +
058        "jcs.default.elementattributes.IsEternal=false\n" +
059        "jcs.default.elementattributes.MaxLife=700\n" +
060        "jcs.default.elementattributes.IdleTime=1800\n" +
061        "jcs.default.elementattributes.IsSpool=true\n" +
062        "jcs.default.elementattributes.IsRemote=true\n" +
063        "jcs.default.elementattributes.IsLateral=true\n";
064
065    private static class InternalManager extends CompositeCacheManager
066    {
067        protected static InternalManager create()
068        {
069            return new InternalManager();
070        }
071
072        protected CompositeCacheConfigurator newConfigurator()
073        {
074            return new CompositeCacheConfigurator()
075            {
076                @Override
077                protected <K, V> CompositeCache<K, V> newCache(
078                        final ICompositeCacheAttributes cca, final IElementAttributes ea)
079                {
080                    return new ExpiryAwareCache<K, V>( cca, ea );
081                }
082            };
083        }
084
085        @Override // needed to call it from JCSCachingManager
086        protected void initialize() {
087            super.initialize();
088        }
089    }
090
091    private final CachingProvider provider;
092    private final URI uri;
093    private final ClassLoader loader;
094    private final Properties properties;
095    private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<String, Cache<?, ?>>();
096    private final Properties configProperties;
097    private volatile boolean closed = false;
098    private InternalManager delegate = InternalManager.create();
099
100    public JCSCachingManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties)
101    {
102        this.provider = provider;
103        this.uri = uri;
104        this.loader = loader;
105        this.properties = readConfig(uri, loader, properties);
106        this.configProperties = properties;
107
108        delegate.setJmxName(CompositeCacheManager.JMX_OBJECT_NAME
109                + ",provider=" + provider.hashCode()
110                + ",uri=" + uri.toString().replaceAll(",|:|=|\n", ".")
111                + ",classloader=" + loader.hashCode()
112                + ",properties=" + this.properties.hashCode());
113        delegate.initialize();
114        delegate.configure(this.properties);
115    }
116
117    private Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
118        final Properties props = new Properties();
119        try {
120            if (JCSCachingProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.toURL().getProtocol().equals("jcs"))
121            {
122
123                final Enumeration<URL> resources = loader.getResources(uri.getPath());
124                if (!resources.hasMoreElements()) // default
125                {
126                    props.load(new ByteArrayInputStream(DEFAULT_CONFIG.getBytes("UTF-8")));
127                }
128                else
129                {
130                    do
131                    {
132                        addProperties(resources.nextElement(), props);
133                    }
134                    while (resources.hasMoreElements());
135                }
136            }
137            else
138            {
139                props.load(uri.toURL().openStream());
140            }
141        } catch (final IOException e) {
142            throw new IllegalStateException(e);
143        }
144
145        if (properties != null)
146        {
147            props.putAll(properties);
148        }
149
150        for (final Map.Entry<Object, Object> entry : props.entrySet()) {
151            if (entry.getValue() == null)
152            {
153                continue;
154            }
155            final String substitute = SUBSTITUTOR.substitute(entry.getValue().toString());
156            if (!substitute.equals(entry.getValue()))
157            {
158                entry.setValue(substitute);
159            }
160        }
161        return props;
162    }
163
164    private void addProperties(final URL url, final Properties aggregator)
165    {
166        InputStream inStream = null;
167        try
168        {
169            inStream = url.openStream();
170            aggregator.load(inStream);
171        }
172        catch (final IOException e)
173        {
174            throw new IllegalArgumentException(e);
175        }
176        finally
177        {
178            if (inStream != null)
179            {
180                try
181                {
182                    inStream.close();
183                }
184                catch (final IOException e)
185                {
186                    // no-op
187                }
188            }
189        }
190    }
191
192    private void assertNotClosed()
193    {
194        if (isClosed())
195        {
196            throw new IllegalStateException("cache manager closed");
197        }
198    }
199
200    @Override
201    // TODO: use configuration + handle not serializable key/values
202    public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
203            throws IllegalArgumentException
204    {
205        assertNotClosed();
206        assertNotNull(cacheName, "cacheName");
207        assertNotNull(configuration, "configuration");
208        final Class<?> keyType = configuration == null ? Object.class : configuration.getKeyType();
209        final Class<?> valueType = configuration == null ? Object.class : configuration.getValueType();
210        if (!caches.containsKey(cacheName))
211        {
212            final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader,
213                    new JCSCache/*<K, V>*/(
214                            loader, this, cacheName,
215                            new JCSConfiguration/*<K, V>*/(configuration, keyType, valueType),
216                            properties,
217                            ExpiryAwareCache.class.cast(delegate.getCache(cacheName))));
218            caches.putIfAbsent(cacheName, cache);
219        }
220        else
221        {
222            throw new javax.cache.CacheException("cache " + cacheName + " already exists");
223        }
224        return (Cache<K, V>) getCache(cacheName, keyType, valueType);
225    }
226
227    @Override
228    public void destroyCache(final String cacheName)
229    {
230        assertNotClosed();
231        assertNotNull(cacheName, "cacheName");
232        final Cache<?, ?> cache = caches.remove(cacheName);
233        if (cache != null && !cache.isClosed())
234        {
235            cache.clear();
236            cache.close();
237        }
238    }
239
240    @Override
241    public void enableManagement(final String cacheName, final boolean enabled)
242    {
243        assertNotClosed();
244        assertNotNull(cacheName, "cacheName");
245        final JCSCache<?, ?> cache = getJCSCache(cacheName);
246        if (cache != null)
247        {
248            if (enabled)
249            {
250                cache.enableManagement();
251            }
252            else
253            {
254                cache.disableManagement();
255            }
256        }
257    }
258
259    private JCSCache<?, ?> getJCSCache(final String cacheName)
260    {
261        final Cache<?, ?> cache = caches.get(cacheName);
262        return JCSCache.class.cast(ClassLoaderAwareCache.getDelegate(cache));
263    }
264
265    @Override
266    public void enableStatistics(final String cacheName, final boolean enabled)
267    {
268        assertNotClosed();
269        assertNotNull(cacheName, "cacheName");
270        final JCSCache<?, ?> cache = getJCSCache(cacheName);
271        if (cache != null)
272        {
273            if (enabled)
274            {
275                cache.enableStatistics();
276            }
277            else
278            {
279                cache.disableStatistics();
280            }
281        }
282    }
283
284    @Override
285    public synchronized void close()
286    {
287        if (isClosed())
288        {
289            return;
290        }
291
292        assertNotClosed();
293        for (final Cache<?, ?> c : caches.values())
294        {
295            c.close();
296        }
297        caches.clear();
298        closed = true;
299        if (JCSCachingProvider.class.isInstance(provider))
300        {
301            JCSCachingProvider.class.cast(provider).remove(this);
302        }
303        delegate.shutDown();
304    }
305
306    @Override
307    public <T> T unwrap(final Class<T> clazz)
308    {
309        if (clazz.isInstance(this))
310        {
311            return clazz.cast(this);
312        }
313        throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
314    }
315
316    @Override
317    public boolean isClosed()
318    {
319        return closed;
320    }
321
322    @Override
323    public <K, V> Cache<K, V> getCache(final String cacheName)
324    {
325        assertNotClosed();
326        assertNotNull(cacheName, "cacheName");
327        return (Cache<K, V>) doGetCache(cacheName, Object.class, Object.class);
328    }
329
330    @Override
331    public Iterable<String> getCacheNames()
332    {
333        return new ImmutableIterable<String>(caches.keySet());
334    }
335
336    @Override
337    public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
338    {
339        assertNotClosed();
340        assertNotNull(cacheName, "cacheName");
341        assertNotNull(keyType, "keyType");
342        assertNotNull(valueType, "valueType");
343        try
344        {
345            return doGetCache(cacheName, keyType, valueType);
346        }
347        catch (final IllegalArgumentException iae)
348        {
349            throw new ClassCastException(iae.getMessage());
350        }
351    }
352
353    private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
354    {
355        final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
356        if (cache == null)
357        {
358            return null;
359        }
360
361        final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
362        if ((keyType != null && !config.getKeyType().isAssignableFrom(keyType))
363                || (valueType != null && !config.getValueType().isAssignableFrom(valueType)))
364        {
365            throw new IllegalArgumentException("this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName()
366                    + "> " + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
367        }
368        return cache;
369    }
370
371    @Override
372    public CachingProvider getCachingProvider()
373    {
374        return provider;
375    }
376
377    @Override
378    public URI getURI()
379    {
380        return uri;
381    }
382
383    @Override
384    public ClassLoader getClassLoader()
385    {
386        return loader;
387    }
388
389    @Override
390    public Properties getProperties()
391    {
392        return configProperties;
393    }
394
395    public void release(final String name) {
396        caches.remove(name);
397    }
398}