1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package net.sf.tinyjee.cache;
21
22 import net.sf.tinyjee.cache.config.*;
23 import net.sf.tinyjee.collections.WeakIdentityHashMap;
24 import net.sf.tinyjee.concurrent.LockingMap;
25 import net.sf.tinyjee.concurrent.ObjectSource;
26 import net.sf.tinyjee.config.Configurable;
27 import net.sf.tinyjee.config.ConfigurationContext;
28 import net.sf.tinyjee.config.Resource;
29 import net.sf.tinyjee.util.Assert;
30 import org.eclipse.jetty.util.component.AbstractLifeCycle;
31 import org.infinispan.AdvancedCache;
32 import org.infinispan.Cache;
33 import org.infinispan.config.CacheLoaderManagerConfig;
34 import org.infinispan.config.Configuration;
35 import org.infinispan.config.GlobalConfiguration;
36 import org.infinispan.eviction.EvictionStrategy;
37 import org.infinispan.loaders.cluster.ClusterCacheLoaderConfig;
38 import org.infinispan.loaders.jdbm.JdbmCacheStoreConfig;
39 import org.infinispan.manager.DefaultCacheManager;
40 import org.infinispan.manager.EmbeddedCacheManager;
41 import org.infinispan.marshall.VersionAwareMarshaller;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import java.io.File;
46 import java.io.IOException;
47 import java.lang.reflect.Field;
48 import java.lang.reflect.Method;
49 import java.util.*;
50
51 import static net.sf.tinyjee.cache.JNDIJGroupsTransport.CHANNEL_JNDI_NAME;
52 import static net.sf.tinyjee.cache.config.CacheResource.KEY_CHANNEL_JNDI_NAME;
53 import static net.sf.tinyjee.cache.config.CacheResource.getRelatedJGroupConnectionName;
54 import static net.sf.tinyjee.cache.config.DistributionPolicy.STATE_VALUES_ON_DEMAND;
55 import static net.sf.tinyjee.cache.config.DistributionPolicy.STATE_VALUES_ON_FAILURE;
56 import static net.sf.tinyjee.cache.config.PersistentMode.OVERFLOW;
57 import static net.sf.tinyjee.cache.config.PersistentMode.TEMPORARY;
58 import static net.sf.tinyjee.config.Names.*;
59 import static org.infinispan.config.Configuration.CacheMode.*;
60
61
62
63
64
65
66
67
68
69
70
71 public class CacheContext extends AbstractLifeCycle implements Configurable {
72
73
74
75
76 public static final String DEFAULT_CACHE_REGION = "default-cache-region";
77
78 private static final Logger log = LoggerFactory.getLogger(CacheContext.class);
79 private static final CacheContext instance = new CacheContext();
80
81 public static CacheContext getInstance() {
82 return instance;
83 }
84
85 static long calculateValue(String expression, long defaultValue) {
86 if (expression != null && !expression.isEmpty()) {
87 return ConfigurationContext.getInstance().
88 createValueExpression(expression, Long.class).getNumber().longValue();
89 }
90 return defaultValue;
91 }
92
93 private String defaultCacheManagerName;
94 private Map<String, EmbeddedCacheManager> cacheManagers = new LockingMap<String, EmbeddedCacheManager>();
95 private Map configuredBeans = new WeakIdentityHashMap();
96
97 private CacheContext() {
98 configure();
99 }
100
101
102
103
104 public void configure() {
105 for (Resource resource : CacheResource.getCacheResources()) {
106 final EmbeddedCacheManager cacheManager;
107 final String name = resource.getName();
108
109 String config = resource.getPropertyValue(CacheResource.KEY_CACHE_CONFIGURATION, "");
110 if (!config.isEmpty()) {
111 if (!config.endsWith(".xml"))
112 config += ".xml";
113 File configFile = new File(config);
114 if (!configFile.exists())
115 configFile = new File(Configurable.Util.getConfigDirectory(), config);
116 if (!configFile.exists()) {
117 try {
118 try {
119 Configurable.Util.copyIfMissing(config, configFile);
120 } catch (IOException e) {
121 Configurable.Util.copyIfMissing("/net/sf/tinyjee/cache/config/" + config, configFile);
122 }
123 } catch (IOException e) {
124 throw new RuntimeException("Failed to create cache resource, the configuration '" +
125 config + "' wasn't found inside the classpath, the config folder or the " +
126 "current working directory.");
127 }
128 }
129
130 try {
131 cacheManager = new DefaultCacheManager(configFile.getAbsolutePath(), false);
132 } catch (Exception e) {
133 throw new RuntimeException(e);
134 }
135 } else
136 cacheManager = new DefaultCacheManager(false);
137
138
139 configureTransport(resource, cacheManager);
140
141 final GlobalConfiguration globalConfiguration = cacheManager.getGlobalConfiguration();
142
143
144 globalConfiguration.setJmxDomain("Infinispan-" + name);
145
146
147 if (VersionAwareMarshaller.class.getName().equals(globalConfiguration.getMarshallerClass()))
148 globalConfiguration.setMarshallerClass(NullAndVersionAwareMarshaller.class.getName());
149
150 resource.bindRelatedInstanceToJNDI(cacheManager);
151
152 if (cacheManagers.isEmpty())
153 defaultCacheManagerName = name;
154 cacheManagers.put(name, cacheManager);
155
156
157 for (CacheConfigurationListener listener : CacheConfigurationListener.LISTENERS)
158 listener.cacheManagerCreated(cacheManager);
159 }
160
161 log.info("TJEE-00630:Configured CacheManagers under {}", cacheManagers.keySet());
162 }
163
164 private void configureTransport(Resource resource, EmbeddedCacheManager cacheManager) {
165 final GlobalConfiguration configuration = cacheManager.getGlobalConfiguration();
166 final String transportClass = configuration.getTransportClass();
167
168 final boolean replaceTransportClass =
169 "org.infinispan.remoting.transport.jgroups.JGroupsTransport".equals(transportClass);
170
171 if (replaceTransportClass) {
172 configuration.setTransportClass(JNDIJGroupsTransport.class.getName());
173 log.info("TJEE-00930:Changing Infinispan transport class from '{}' to '{}'",
174 transportClass, JNDIJGroupsTransport.class.getName());
175 }
176
177 if (replaceTransportClass || transportClass != null) {
178 final String jndiName = resource.getPropertyValue(
179 KEY_CHANNEL_JNDI_NAME, getRelatedJGroupConnectionName(resource));
180
181 configuration.getTransportProperties().setProperty(CHANNEL_JNDI_NAME, jndiName);
182 cacheManager.addListener(new ViewChangedLoggingListener());
183
184 if (log.isDebugEnabled())
185 log.debug("Added Infinispan transport property '{}' => '{}'", CHANNEL_JNDI_NAME, jndiName);
186 }
187 }
188
189
190
191
192 public synchronized void reconfigure() throws Exception {
193 doStop();
194 configure();
195 doStart();
196 }
197
198
199
200
201 @Override
202 protected void doStart() throws Exception {
203 for (EmbeddedCacheManager manager : cacheManagers.values())
204 try {
205 manager.start();
206 } catch (Exception e) {
207 log.error("TJEE-00920:Failed starting cache manager '" + manager + "'", e);
208 }
209
210
211 for (Object configuredBean : configuredBeans.keySet())
212 configure(configuredBean);
213 }
214
215
216
217
218 @Override
219 protected void doStop() throws Exception {
220 for (EmbeddedCacheManager manager : cacheManagers.values()) {
221 try {
222 try {
223 for (String name : manager.getCacheNames()) {
224 try {
225 Cache c = manager.getCache(name);
226 c.stop();
227 } catch (Exception e) {
228 log.error("TJEE-00940:Failed stopping cache '" + name + "'", e);
229 }
230 }
231 } finally {
232 manager.stop();
233 }
234 } catch (Exception e) {
235 log.error("TJEE-00910:Failed stopping cache manager '" + manager + "'", e);
236 }
237 }
238 cacheManagers.clear();
239 }
240
241
242
243
244
245
246 public EmbeddedCacheManager getDefaultCacheManager() {
247 return cacheManagers.get(defaultCacheManagerName);
248 }
249
250
251
252
253
254
255 public Map<String, EmbeddedCacheManager> getCacheManagers() {
256 return Collections.unmodifiableMap(cacheManagers);
257 }
258
259
260
261
262
263
264
265 public EmbeddedCacheManager getCacheManager(String name) {
266 EmbeddedCacheManager manager = cacheManagers.get(name);
267 return manager == null ? getDefaultCacheManager() : manager;
268 }
269
270
271
272
273
274
275
276
277 Configuration createBaseConfiguration(EmbeddedCacheManager cm, String cacheName) {
278 Configuration config = cm.defineConfiguration(cacheName, DEFAULT_CACHE_REGION, new Configuration());
279 config.setExposeJmxStatistics(RecordCacheStatistics.getBoolean());
280 return config;
281 }
282
283
284
285
286
287
288
289
290 Configuration createConfiguration(String cacheName, CacheRegion cacheRegion) {
291 EmbeddedCacheManager cm = getCacheManager(cacheRegion.cacheManager());
292 Configuration config = createBaseConfiguration(cm, cacheName);
293 configure(cm, config, cacheRegion);
294 return cm.defineConfiguration(cacheName, config);
295 }
296
297
298
299
300
301
302
303
304 void configure(EmbeddedCacheManager cm, Configuration config, CacheRegion region) {
305
306 for (CacheConfigurationListener listener : CacheConfigurationListener.LISTENERS)
307 listener.cacheRegionPreConfigured(config, region);
308
309 configureEviction(config, region);
310 configureExpiration(config, region);
311 configureCacheLoadingAndPersistence(config, region);
312
313 if (region.isolationLevel() != IsolationLevel.DEFAULT)
314 config.setIsolationLevel(region.isolationLevel().toString());
315
316 if (cm.getGlobalConfiguration().getTransportClass() != null)
317 configureDistribution(config, region);
318
319
320 for (CacheConfigurationListener listener : CacheConfigurationListener.LISTENERS)
321 listener.cacheRegionConfigured(config, region);
322 }
323
324 private void configureExpiration(Configuration config, CacheRegion region) {
325 long time = calculateValue(region.idleTimeExpression(), region.idleTime());
326 if (time != -1L && config.getExpirationMaxIdle() == -1L)
327 config.setExpirationMaxIdle(region.idleTimeUnit().toMillis(time));
328
329 time = calculateValue(region.defaultExpirationTimeExpression(), region.defaultExpirationTime());
330 if (time != -1L && config.getExpirationLifespan() == -1L)
331 config.setExpirationLifespan(region.defaultExpirationTimeUnit().toMillis(time));
332
333 if (log.isTraceEnabled()) {
334 log.trace("Configured default cache expiration for region '{}' to max-idle {} ms, lifespan {} ms",
335 new Object[]{region.name(), config.getExpirationMaxIdle(), config.getExpirationLifespan()});
336 }
337 }
338
339 private void configureDistribution(Configuration config, CacheRegion region) {
340 Configuration.CacheMode cm = INVALIDATION_SYNC;
341 switch (region.distributionPolicy()) {
342 case NONE:
343 cm = LOCAL;
344 break;
345 case REPLICATE_VALUES:
346 cm = REPL_SYNC;
347 break;
348 case DISTRIBUTE_VALUES:
349 cm = DIST_SYNC;
350 break;
351 }
352
353 if (region.distributionIsAsynchronous()) {
354 cm = cm.toAsync();
355 if (cm == REPL_ASYNC)
356 config.setUseReplQueue(true);
357 }
358
359 if (config.getCacheMode() == LOCAL)
360 config.setCacheMode(cm);
361 else if (log.isTraceEnabled()) {
362 log.trace("Not setting cache mode for region '{}' to '{}' it was already set to '{}'",
363 new Object[]{region.name(), cm, config.getCacheMode()});
364 }
365 }
366
367 private void configureCacheLoadingAndPersistence(Configuration config, CacheRegion region) {
368
369 final CacheLoaderManagerConfig mc = config.getCacheLoaderManagerConfig();
370 if (mc.getFirstCacheLoaderConfig() == null) {
371 mc.setPreload(region.preloadOnStartup());
372
373
374 PersistentMode persistentMode = region.persistentMode();
375 if (UseDiskCaching.getBoolean() && persistentMode != PersistentMode.NONE) {
376
377 JdbmCacheStoreConfig clc = new JdbmCacheStoreConfig();
378 clc.setLocation(DiskCacheStoragePath.getString());
379 clc.getAsyncStoreConfig().setEnabled(persistentMode.isAsynchronous());
380 clc.setPurgeOnStartup(TEMPORARY == persistentMode);
381
382 mc.addCacheLoaderConfig(clc);
383 mc.setPassivation(EnumSet.of(OVERFLOW, TEMPORARY).contains(persistentMode));
384
385 if (log.isTraceEnabled()) {
386 log.trace("Added a persistent cache store for region '{}', using mode '{}'",
387 region.name(), persistentMode);
388 }
389 }
390
391
392 if (EnumSet.of(STATE_VALUES_ON_DEMAND, STATE_VALUES_ON_FAILURE).contains(region.distributionPolicy())) {
393 ClusterCacheLoaderConfig cclc = new ClusterCacheLoaderConfig();
394 mc.addCacheLoaderConfig(cclc);
395
396 if (log.isTraceEnabled())
397 log.trace("Added a cluster aware cache loader for region '{}'", region.name());
398 }
399 } else {
400 if (log.isTraceEnabled()) {
401 log.trace("Not adding cache loaders or stores to region '{}', as {} are already defined.",
402 region.name(), mc.getCacheLoaderConfigs());
403 }
404 }
405 }
406
407 private void configureEviction(Configuration config, CacheRegion region) {
408 try {
409 EvictionStrategy strategy = EvictionStrategy.valueOf(region.evictionPolicy().name());
410 if (config.getEvictionStrategy() == EvictionStrategy.NONE)
411 config.setEvictionStrategy(strategy);
412 } catch (RuntimeException e) {
413 log.warn("TJEE-00620:Not setting unsupported eviction strategy {} on cache region {}",
414 region.evictionPolicy(), region.name());
415 }
416
417 if (config.getEvictionMaxEntries() == -1) {
418 int elementsInMem = (int) calculateValue(
419 region.maxElementsInMemoryExpression(), region.maxElementsInMemory());
420 config.setEvictionMaxEntries(elementsInMem);
421 }
422
423 if (log.isTraceEnabled()) {
424 log.trace("Keeping max {} entries in memory before evicting them with a {} strategy in cache region '{}'.",
425 new Object[]{config.getEvictionMaxEntries(), config.getEvictionStrategy(), region.name()});
426 }
427 }
428
429
430
431
432
433
434
435 @SuppressWarnings("unchecked")
436 public synchronized void configure(Object bean) {
437 List<Class<?>> classHierarchy = new ArrayList<Class<?>>();
438 for (Class<?> c = bean.getClass(); c != null; c = c.getSuperclass())
439 classHierarchy.add(c);
440
441
442 Collections.reverse(classHierarchy);
443
444
445 for (Class<?> beanType : classHierarchy)
446 processCacheRegions(beanType);
447
448
449 boolean containsCacheRegion = false;
450 for (Class<?> beanType : classHierarchy)
451 if (processFields(beanType, bean))
452 containsCacheRegion = true;
453
454 if (containsCacheRegion)
455 configuredBeans.put(bean, this);
456 }
457
458
459
460
461
462
463
464
465 boolean processFields(Class<?> type, Object instance) {
466 boolean containsCacheRegion = false;
467 if (type != null) {
468 for (Field field : type.getDeclaredFields()) {
469 CacheRegion region = field.getAnnotation(CacheRegion.class);
470 if (region != null) {
471 containsCacheRegion = true;
472
473
474 if (!isStarted() && !isStarting())
475 break;
476
477 String cacheName = getCacheName(region, field);
478 declareCacheIfNeeded(cacheName, region);
479 Cache cache = get(cacheName, region);
480
481 Method setter = null;
482 String fieldName = field.getName();
483 String setterName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
484 try {
485
486
487 setter = instance.getClass().getMethod(setterName, field.getType());
488 } catch (NoSuchMethodException e) {
489 if (log.isTraceEnabled()) {
490 log.trace("Did not find setter for field (" + field.toGenericString() +
491 "), setting the cache instance for '" + cacheName + "' directly.");
492 }
493 }
494
495 try {
496 if (field.getType().isAssignableFrom(AdvancedCache.class))
497 cache = cache.getAdvancedCache();
498
499 if (field.getType().isAssignableFrom(Cache.class)) {
500 if (setter == null) {
501 if (!field.isAccessible())
502 field.setAccessible(true);
503 field.set(instance, cache);
504 } else
505 setter.invoke(instance, cache);
506 } else {
507 String msg = "Found a field (" + field.toGenericString() + ") that was annotated with " +
508 "CacheRegion but did not use a one of the supported types: " +
509 "'org.infinispan.Cache', 'java.util.concurrent.ConcurrentMap<?,?>' or " +
510 "'java.util.Map<?, ?>'.";
511 throw new IllegalArgumentException(msg);
512 }
513 } catch (Exception e) {
514 throw new RuntimeException();
515 }
516 }
517 }
518 }
519 return containsCacheRegion;
520 }
521
522
523
524
525
526
527 void processCacheRegions(Class<?> type) {
528 if (type == null)
529 return;
530
531 final Package typePackage = type.getPackage();
532 CacheRegions[] allRegions = {
533 type.getAnnotation(CacheRegions.class),
534 typePackage == null ? null : typePackage.getAnnotation(CacheRegions.class)
535 };
536
537 for (CacheRegions regions : allRegions) {
538 if (regions != null)
539 for (CacheRegion region : regions.value()) {
540 if (region.name().isEmpty()) {
541 String msg = "A CacheRegion that was specified inside a CacheRegions annotation " +
542 "must have a non-empty region name because the default naming scheme " +
543 "cannot be applied. Look at the regions annotation defined inside " +
544 "'" + type + "' or its package info.";
545 throw new IllegalArgumentException(msg);
546 }
547
548 EmbeddedCacheManager cm = getCacheManager(region.cacheManager());
549 if (cm == null)
550 continue;
551
552 String cacheName = getCacheName(region, null, null);
553 declareCacheIfNeeded(cm, cacheName, region);
554 }
555 }
556 }
557
558 boolean declareCacheIfNeeded(String cacheName, CacheRegion region) {
559 EmbeddedCacheManager cm = getCacheManager(region.cacheManager());
560 return cm != null && declareCacheIfNeeded(cm, cacheName, region);
561 }
562
563 boolean declareCacheIfNeeded(EmbeddedCacheManager cm, String cacheName, CacheRegion region) {
564 if (!cm.getCacheNames().contains(cacheName))
565 return declareCache(cm, cacheName, createConfiguration(cacheName, region), region);
566 return false;
567 }
568
569
570
571
572
573
574
575
576
577 boolean declareCache(EmbeddedCacheManager cm, String cacheName,
578 Configuration configuration, CacheRegion cacheRegion) {
579 try {
580
581 if (cacheRegion.valueFactory() != ObjectSource.class) {
582 ObjectSourceCacheLoaderConfig loaderConfig = new ObjectSourceCacheLoaderConfig();
583 loaderConfig.setObjectSourceClass(cacheRegion.valueFactory().getName());
584 CacheLoaderManagerConfig managerConfig = configuration.getCacheLoaderManagerConfig();
585 managerConfig.addCacheLoaderConfig(loaderConfig);
586 }
587
588 configuration = cm.defineConfiguration(cacheName, cacheRegion.inheritFrom(), configuration);
589 cm.getCache(cacheName);
590
591 if (log.isTraceEnabled()) {
592 log.trace("Declared new cache '{}' with configuration '{}'",
593 cacheName, configurationToXmlString(configuration));
594 } else {
595 log.info("TJEE-01010:Configured cache region '{}' with '{}'",
596 cacheName, configurationToString(configuration));
597 }
598
599 return true;
600 } catch (Throwable t) {
601 log.error("TJEE-00950:Failed declaring cache '" + cacheName + "' with configuration '" +
602 configurationToXmlString(configuration) + "', caused by ", t);
603 return false;
604 }
605 }
606
607 private String configurationToString(Configuration configuration) {
608 StringBuilder b = new StringBuilder(128);
609 b.append("CacheConfiguration{").
610 append("elementsInHeap=").append(configuration.getEvictionMaxEntries()).
611 append(", eviction=").append(configuration.getEvictionStrategy()).
612 append(", idleTime=").append(configuration.getExpirationMaxIdle()).
613 append(", expireTime=").append(configuration.getExpirationLifespan()).
614 append(", persistent=").append(configuration.getCacheLoaderManagerConfig().getFirstCacheLoaderConfig() != null).
615 append(", cacheMode=").append(configuration.getCacheMode());
616 return b.append(" }").toString();
617 }
618
619 private String configurationToXmlString(Configuration configuration) {
620 try {
621 return new InfinispanNamedCacheFragment(configuration).toString();
622 } catch (Exception e) {
623 return configuration.toString();
624 }
625 }
626
627
628
629
630
631
632
633
634 Cache get(String cacheName, CacheRegion cacheRegion) {
635 EmbeddedCacheManager cm = getCacheManager(cacheRegion.cacheManager());
636 if (cm == null)
637 return null;
638
639 return cm.getCache(cacheName);
640 }
641
642
643
644
645
646
647
648
649 String getCacheName(CacheRegion cacheRegion, Field field) {
650 return getCacheName(cacheRegion, field.getDeclaringClass(), field.getName());
651 }
652
653
654
655
656
657
658
659
660
661 String getCacheName(CacheRegion cacheRegion, Class target, String fieldName) {
662 if (cacheRegion.name().isEmpty()) {
663 Assert.assertNotNull("target", target);
664 Assert.assertNotNull("fieldName", fieldName);
665 return target.getSimpleName() + "." + fieldName;
666 }
667 return cacheRegion.name();
668 }
669 }