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}