View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.jcs3.jcache;
20  
21  import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URI;
27  import java.net.URL;
28  import java.nio.charset.StandardCharsets;
29  import java.util.Enumeration;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ConcurrentMap;
34  
35  import javax.cache.Cache;
36  import javax.cache.CacheManager;
37  import javax.cache.configuration.Configuration;
38  import javax.cache.spi.CachingProvider;
39  
40  import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
41  import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
42  import org.apache.commons.jcs3.engine.control.CompositeCache;
43  import org.apache.commons.jcs3.engine.control.CompositeCacheConfigurator;
44  import org.apache.commons.jcs3.engine.control.CompositeCacheManager;
45  import org.apache.commons.jcs3.jcache.lang.Subsitutor;
46  import org.apache.commons.jcs3.jcache.proxy.ClassLoaderAwareCache;
47  
48  public class JCSCachingManager implements CacheManager
49  {
50      private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE;
51      private static final String DEFAULT_CONFIG =
52          "jcs.default=DC\n" +
53          "jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes\n" +
54          "jcs.default.cacheattributes.MaxObjects=200001\n" +
55          "jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache\n" +
56          "jcs.default.cacheattributes.UseMemoryShrinker=true\n" +
57          "jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" +
58          "jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" +
59          "jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes\n" +
60          "jcs.default.elementattributes.IsEternal=false\n" +
61          "jcs.default.elementattributes.MaxLife=700\n" +
62          "jcs.default.elementattributes.IdleTime=1800\n" +
63          "jcs.default.elementattributes.IsSpool=true\n" +
64          "jcs.default.elementattributes.IsRemote=true\n" +
65          "jcs.default.elementattributes.IsLateral=true\n";
66  
67      private static class InternalManager extends CompositeCacheManager
68      {
69          protected static InternalManager create()
70          {
71              return new InternalManager();
72          }
73  
74          @Override
75          protected CompositeCacheConfigurator newConfigurator()
76          {
77              return new CompositeCacheConfigurator()
78              {
79                  @Override
80                  protected <K, V> CompositeCache<K, V> newCache(
81                          final ICompositeCacheAttributes cca, final IElementAttributes ea)
82                  {
83                      return new ExpiryAwareCache<>( cca, ea );
84                  }
85              };
86          }
87  
88          @Override // needed to call it from JCSCachingManager
89          protected void initialize() {
90              super.initialize();
91          }
92      }
93  
94      private final CachingProvider provider;
95      private final URI uri;
96      private final ClassLoader loader;
97      private final Properties properties;
98      private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<>();
99      private final Properties configProperties;
100     private volatile boolean closed;
101     private final InternalManager delegate = InternalManager.create();
102 
103     public JCSCachingManager(final CachingProvider provider, final URI uri, final ClassLoader loader, final Properties properties)
104     {
105         this.provider = provider;
106         this.uri = uri;
107         this.loader = loader;
108         this.properties = readConfig(uri, loader, properties);
109         this.configProperties = properties;
110 
111         delegate.setJmxName(CompositeCacheManager.JMX_OBJECT_NAME
112                 + ",provider=" + provider.hashCode()
113                 + ",uri=" + uri.toString().replaceAll(",|:|=|\n", ".")
114                 + ",classloader=" + loader.hashCode()
115                 + ",properties=" + this.properties.hashCode());
116         delegate.initialize();
117         delegate.configure(this.properties);
118     }
119 
120     private static Properties readConfig(final URI uri, final ClassLoader loader, final Properties properties) {
121         final Properties props = new Properties();
122         try {
123             if (JCSCachingProvider.DEFAULT_URI.toString().equals(uri.toString()) || uri.toURL().getProtocol().equals("jcs"))
124             {
125 
126                 final Enumeration<URL> resources = loader.getResources(uri.getPath());
127                 if (!resources.hasMoreElements()) // default
128                 {
129                     props.load(new ByteArrayInputStream(DEFAULT_CONFIG.getBytes(StandardCharsets.UTF_8)));
130                 }
131                 else
132                 {
133                     do
134                     {
135                         addProperties(resources.nextElement(), props);
136                     }
137                     while (resources.hasMoreElements());
138                 }
139             }
140             else
141             {
142                 props.load(uri.toURL().openStream());
143             }
144         } catch (final IOException e) {
145             throw new IllegalStateException(e);
146         }
147 
148         if (properties != null)
149         {
150             props.putAll(properties);
151         }
152 
153         for (final Map.Entry<Object, Object> entry : props.entrySet()) {
154             if (entry.getValue() == null)
155             {
156                 continue;
157             }
158             final String substitute = SUBSTITUTOR.substitute(entry.getValue().toString());
159             if (!substitute.equals(entry.getValue()))
160             {
161                 entry.setValue(substitute);
162             }
163         }
164         return props;
165     }
166 
167     private static void addProperties(final URL url, final Properties aggregator)
168     {
169         try (InputStream inStream = url.openStream()) {
170             aggregator.load(inStream);
171         } catch (final IOException e) {
172             throw new IllegalArgumentException(e);
173         }
174     }
175 
176     private void assertNotClosed()
177     {
178         if (isClosed())
179         {
180             throw new IllegalStateException("cache manager closed");
181         }
182     }
183 
184     @Override
185     // TODO: use configuration + handle not serializable key/values
186     public <K, V, C extends Configuration<K, V>> Cache<K, V> createCache(final String cacheName, final C configuration)
187             throws IllegalArgumentException
188     {
189         assertNotClosed();
190         assertNotNull(cacheName, "cacheName");
191         assertNotNull(configuration, "configuration");
192         final Class<K> keyType = configuration.getKeyType();
193         final Class<V> valueType = configuration.getValueType();
194         if (caches.containsKey(cacheName)) {
195             throw new javax.cache.CacheException("cache " + cacheName + " already exists");
196         }
197         @SuppressWarnings("unchecked")
198         final Cache<K, V> cache = ClassLoaderAwareCache.wrap(loader,
199                 new JCSCache<>(
200                         loader, this, cacheName,
201                         new JCSConfiguration<K, V>(configuration, keyType, valueType),
202                         properties,
203                         ExpiryAwareCache.class.cast(delegate.getCache(cacheName))));
204         caches.putIfAbsent(cacheName, cache);
205         return getCache(cacheName, keyType, valueType);
206     }
207 
208     @Override
209     public void destroyCache(final String cacheName)
210     {
211         assertNotClosed();
212         assertNotNull(cacheName, "cacheName");
213         final Cache<?, ?> cache = caches.remove(cacheName);
214         if (cache != null && !cache.isClosed())
215         {
216             cache.clear();
217             cache.close();
218         }
219     }
220 
221     @Override
222     public void enableManagement(final String cacheName, final boolean enabled)
223     {
224         assertNotClosed();
225         assertNotNull(cacheName, "cacheName");
226         final JCSCache<?, ?> cache = getJCSCache(cacheName);
227         if (cache != null)
228         {
229             if (enabled)
230             {
231                 cache.enableManagement();
232             }
233             else
234             {
235                 cache.disableManagement();
236             }
237         }
238     }
239 
240     private JCSCache<?, ?> getJCSCache(final String cacheName)
241     {
242         final Cache<?, ?> cache = caches.get(cacheName);
243         return JCSCache.class.cast(ClassLoaderAwareCache.getDelegate(cache));
244     }
245 
246     @Override
247     public void enableStatistics(final String cacheName, final boolean enabled)
248     {
249         assertNotClosed();
250         assertNotNull(cacheName, "cacheName");
251         final JCSCache<?, ?> cache = getJCSCache(cacheName);
252         if (cache != null)
253         {
254             if (enabled)
255             {
256                 cache.enableStatistics();
257             }
258             else
259             {
260                 cache.disableStatistics();
261             }
262         }
263     }
264 
265     @Override
266     public synchronized void close()
267     {
268         if (isClosed())
269         {
270             return;
271         }
272 
273         assertNotClosed();
274         for (final Cache<?, ?> c : caches.values())
275         {
276             c.close();
277         }
278         caches.clear();
279         closed = true;
280         if (JCSCachingProvider.class.isInstance(provider))
281         {
282             JCSCachingProvider.class.cast(provider).remove(this);
283         }
284         delegate.shutDown();
285     }
286 
287     @Override
288     public <T> T unwrap(final Class<T> clazz)
289     {
290         if (clazz.isInstance(this))
291         {
292             return clazz.cast(this);
293         }
294         throw new IllegalArgumentException(clazz.getName() + " not supported in unwrap");
295     }
296 
297     @Override
298     public boolean isClosed()
299     {
300         return closed;
301     }
302 
303     @Override
304     public <K, V> Cache<K, V> getCache(final String cacheName)
305     {
306         assertNotClosed();
307         assertNotNull(cacheName, "cacheName");
308         return (Cache<K, V>) doGetCache(cacheName, Object.class, Object.class);
309     }
310 
311     @Override
312     public Iterable<String> getCacheNames()
313     {
314         return new ImmutableIterable<>(caches.keySet());
315     }
316 
317     @Override
318     public <K, V> Cache<K, V> getCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
319     {
320         assertNotClosed();
321         assertNotNull(cacheName, "cacheName");
322         assertNotNull(keyType, "keyType");
323         assertNotNull(valueType, "valueType");
324         try
325         {
326             return doGetCache(cacheName, keyType, valueType);
327         }
328         catch (final IllegalArgumentException iae)
329         {
330             throw new ClassCastException(iae.getMessage());
331         }
332     }
333 
334     private <K, V> Cache<K, V> doGetCache(final String cacheName, final Class<K> keyType, final Class<V> valueType)
335     {
336         @SuppressWarnings("unchecked") // common map for all caches
337         final Cache<K, V> cache = (Cache<K, V>) caches.get(cacheName);
338         if (cache == null)
339         {
340             return null;
341         }
342 
343         @SuppressWarnings("unchecked") // don't know how to solve this
344         final Configuration<K, V> config = cache.getConfiguration(Configuration.class);
345         if (keyType != null && !config.getKeyType().isAssignableFrom(keyType) ||
346             valueType != null && !config.getValueType().isAssignableFrom(valueType))
347         {
348             throw new IllegalArgumentException("this cache is <" + config.getKeyType().getName() + ", " + config.getValueType().getName()
349                     + "> " + " and not <" + keyType.getName() + ", " + valueType.getName() + ">");
350         }
351         return cache;
352     }
353 
354     @Override
355     public CachingProvider getCachingProvider()
356     {
357         return provider;
358     }
359 
360     @Override
361     public URI getURI()
362     {
363         return uri;
364     }
365 
366     @Override
367     public ClassLoader getClassLoader()
368     {
369         return loader;
370     }
371 
372     @Override
373     public Properties getProperties()
374     {
375         return configProperties;
376     }
377 
378     public void release(final String name) {
379         caches.remove(name);
380     }
381 }