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.jcs3.jcache; 020 021import static org.apache.commons.jcs3.jcache.Asserts.assertNotNull; 022 023import java.io.ByteArrayInputStream; 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.URI; 027import java.net.URL; 028import java.nio.charset.StandardCharsets; 029import java.util.Enumeration; 030import java.util.Map; 031import java.util.Properties; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.ConcurrentMap; 034 035import javax.cache.Cache; 036import javax.cache.CacheManager; 037import javax.cache.configuration.Configuration; 038import javax.cache.spi.CachingProvider; 039 040import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes; 041import org.apache.commons.jcs3.engine.behavior.IElementAttributes; 042import org.apache.commons.jcs3.engine.control.CompositeCache; 043import org.apache.commons.jcs3.engine.control.CompositeCacheConfigurator; 044import org.apache.commons.jcs3.engine.control.CompositeCacheManager; 045import org.apache.commons.jcs3.jcache.lang.Subsitutor; 046import org.apache.commons.jcs3.jcache.proxy.ClassLoaderAwareCache; 047 048public class JCSCachingManager implements CacheManager 049{ 050 private static final Subsitutor SUBSTITUTOR = Subsitutor.Helper.INSTANCE; 051 private static final String DEFAULT_CONFIG = 052 "jcs.default=DC\n" + 053 "jcs.default.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes\n" + 054 "jcs.default.cacheattributes.MaxObjects=200001\n" + 055 "jcs.default.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache\n" + 056 "jcs.default.cacheattributes.UseMemoryShrinker=true\n" + 057 "jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600\n" + 058 "jcs.default.cacheattributes.ShrinkerIntervalSeconds=60\n" + 059 "jcs.default.elementattributes=org.apache.commons.jcs3.engine.ElementAttributes\n" + 060 "jcs.default.elementattributes.IsEternal=false\n" + 061 "jcs.default.elementattributes.MaxLife=700\n" + 062 "jcs.default.elementattributes.IdleTime=1800\n" + 063 "jcs.default.elementattributes.IsSpool=true\n" + 064 "jcs.default.elementattributes.IsRemote=true\n" + 065 "jcs.default.elementattributes.IsLateral=true\n"; 066 067 private static class InternalManager extends CompositeCacheManager 068 { 069 protected static InternalManager create() 070 { 071 return new InternalManager(); 072 } 073 074 @Override 075 protected CompositeCacheConfigurator newConfigurator() 076 { 077 return new CompositeCacheConfigurator() 078 { 079 @Override 080 protected <K, V> CompositeCache<K, V> newCache( 081 final ICompositeCacheAttributes cca, final IElementAttributes ea) 082 { 083 return new ExpiryAwareCache<>( cca, ea ); 084 } 085 }; 086 } 087 088 @Override // needed to call it from JCSCachingManager 089 protected void initialize() { 090 super.initialize(); 091 } 092 } 093 094 private final CachingProvider provider; 095 private final URI uri; 096 private final ClassLoader loader; 097 private final Properties properties; 098 private final ConcurrentMap<String, Cache<?, ?>> caches = new ConcurrentHashMap<>(); 099 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}