cleanup
[cdmlib.git] / cdmlib-cache / src / main / java / eu / etaxonomy / cdm / cache / CdmTransientEntityCacher.java
1 /**
2 * Copyright (C) 2014 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.cache;
10
11 import java.lang.management.ManagementFactory;
12 import java.util.ArrayList;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.UUID;
18
19 import javax.management.MBeanServer;
20
21 import org.apache.log4j.Logger;
22
23 import eu.etaxonomy.cdm.api.cache.CdmCacher;
24 import eu.etaxonomy.cdm.model.ICdmCacher;
25 import eu.etaxonomy.cdm.model.common.CdmBase;
26 import eu.etaxonomy.cdm.persistence.dto.MergeResult;
27 import net.sf.ehcache.Cache;
28 import net.sf.ehcache.CacheManager;
29 import net.sf.ehcache.Element;
30 import net.sf.ehcache.Status;
31 import net.sf.ehcache.config.CacheConfiguration;
32 import net.sf.ehcache.config.SizeOfPolicyConfiguration;
33 import net.sf.ehcache.management.ManagementService;
34 import net.sf.ehcache.statistics.LiveCacheStatistics;
35
36 /**
37 * This cache handles transient (id>0) and volatile (id=0) CdmBase objects.
38 * Volatile objects need to be added via {@link #putVolatitleEntity(CdmBase)}
39 * and their id is updated as soon as a transient object with the same
40 * uuid is added to the cacher.
41 *
42 * This cache guarantees that
43 * - all objects put will be ancestors of CdmBase
44 * - all CdmBase objects in the cache will be already de-proxied
45 * - after any CdmBase object is put in the cache,
46 * all non-null / non-proxy CdmBase objects in the sub-graph
47 * will also be present in the cache.
48 *
49 * @author cmathew
50 * @since 14 Oct 2014
51 */
52 public class CdmTransientEntityCacher implements ICdmCacher {
53
54 private static final Logger logger = Logger.getLogger(CdmTransientEntityCacher.class);
55
56 //the key for this cacher within the CacheManager
57 private final String cacheId;
58
59 //the cache
60 private final Cache cache;
61
62 //permanent cache which is usually used to cache terms permanently
63 private static CdmCacher permanentCache;
64
65 private final CacheLoader cacheLoader;
66
67 //map for volatile entities (id=0)
68 private final Map<UUID, CdmBase> volatileEntitiesMap = new HashMap<>();
69
70 private static volatile boolean managementBeansConfigured = false;
71
72 // ********************* CONSTRUCTOR **********************************/
73
74 public CdmTransientEntityCacher(String cacheId) {
75 this.cacheId = cacheId;
76
77 cache = new Cache(getEntityCacheConfiguration(cacheId));
78
79 getCacheManager().removeCache(cache.getName());
80 getCacheManager().addCache(cache);
81
82 cacheLoader = new CacheLoader(this);
83 }
84
85 public CdmTransientEntityCacher(Object sessionOwner) {
86 this(generateCacheId(sessionOwner));
87 }
88
89 //****************************** STATIC METHODS *********************************/
90
91 /**
92 * Generates an id for this session.
93 * @param sessionOwner
94 * @return
95 */
96 private static String generateCacheId(Object sessionOwner) {
97 return sessionOwner.getClass().getName() + String.valueOf(sessionOwner.hashCode());
98 }
99
100 public static <T extends CdmBase> CdmEntityCacheKey<T> generateKey(Class<T> clazz, int id) {
101 return new CdmEntityCacheKey<T>(clazz, id);
102 }
103
104 public static <T extends CdmBase> CdmEntityCacheKey<T> generateKey(T cdmBase) {
105 Class<T> entityClass = (Class<T>)cdmBase.getClass();
106 return new CdmEntityCacheKey<T>(entityClass, cdmBase.getId());
107 }
108
109 public static void setPermanentCacher(CdmCacher permanentCacher) {
110 permanentCache = permanentCacher;
111 }
112
113 //****************************** METHODS *********************************/
114
115 /**
116 * Returns the default cache configuration.
117 */
118 private CacheConfiguration getEntityCacheConfiguration(String cacheId) {
119 SizeOfPolicyConfiguration sizeOfConfig = new SizeOfPolicyConfiguration();
120 sizeOfConfig.setMaxDepth(100);
121 sizeOfConfig.setMaxDepthExceededBehavior("abort");
122
123 return new CacheConfiguration(cacheId, 0)
124 .eternal(true)
125 .statistics(true)
126 .sizeOfPolicy(sizeOfConfig)
127 .overflowToOffHeap(false);
128 }
129
130 public LiveCacheStatistics getCacheStatistics() {
131 if(cache.getStatus() == Status.STATUS_ALIVE) {
132 return cache.getLiveCacheStatistics();
133 }
134 return null;
135 }
136
137 /**
138 * Returns the cache corresponding to the cache id
139 */
140 private Cache getCache() {
141 return getCacheManager().getCache(cacheId);
142 }
143
144 /**
145 * @return the singleton cacheManager
146 */
147 protected CacheManager getCacheManager() {
148
149 CacheManager cacheManager = CacheManager.create();
150
151 if(!managementBeansConfigured){
152 MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
153 boolean registerCacheManager = false;
154 boolean registerCaches = true;
155 boolean registerCacheConfigurations = false;
156 boolean registerCacheStatistics = true;
157 ManagementService.registerMBeans(cacheManager, mBeanServer, registerCacheManager, registerCaches, registerCacheConfigurations, registerCacheStatistics);
158 managementBeansConfigured = true;
159 }
160
161 return cacheManager;
162 }
163
164 public <T extends Object> T load(T obj, boolean update) {
165 return cacheLoader.load(obj, true, update);
166 }
167
168 public <T extends Object> Map<T,T> load(Map<T,T> map, boolean update){
169 return cacheLoader.load(map, true, update);
170 }
171
172 public <T extends Object> Collection<T> load(Collection<T> collection, boolean update){
173 return cacheLoader.load(collection, true, update);
174 }
175
176 /**
177 * Loads the {@link eu.etaxonomy.cdm.model.common.CdmBase cdmEntity}) graph recursively into the
178 * cache.
179 *
180 * For in depth details on the whole mechanism see
181 * {@link CacheLoader#load(CdmBase, boolean, boolean)},
182 * {@link CacheLoader#loadRecursive(CdmBase, List, boolean)} and
183 * {@link CacheLoader#getCdmBaseTypeFieldValue(CdmBase, CdmBase, String, List, boolean)}
184 *
185 * @param cdmEntity
186 * the entity to be put into the cache
187 * @param update
188 * all fields of the cached entity will be overwritten by setting
189 * them to the value of the cdm entity being loaded
190 * @return
191 */
192 public <T extends CdmBase> T load(T cdmEntity, boolean update) {
193 return cacheLoader.load(cdmEntity, true, update);
194 }
195
196 public MergeResult<CdmBase> load(MergeResult<CdmBase> mergeResult, boolean update) {
197 return cacheLoader.load(mergeResult, true, update);
198 }
199
200 public CdmModelFieldPropertyFromClass getFromCdmlibModelCache(String className) {
201 return cacheLoader.getFromCdmlibModelCache(className);
202 }
203
204 private void putVolatitleEntity(CdmBase volatileEntity) {
205 if(volatileEntity != null && volatileEntity.getId() == 0 && volatileEntity.getUuid() != null) {
206 CdmBase cachedEntity = volatileEntitiesMap.get(volatileEntity.getUuid());
207 if (cachedEntity == null){
208 volatileEntitiesMap.put(volatileEntity.getUuid(), volatileEntity);
209 }
210 }
211 }
212
213 /**
214 * Puts the passed <code>cdmEntity</code> into the according caches
215 * (cache, newEntitiesMap, permanentCache(TODO still needs to be checked, not implemented yet))
216 * as long it does not yet exist there.
217 * <p>
218 * The adjacent <b>ENTITY GRAPH WILL NOT BE LOADED RECURSIVELY</b>
219 */
220 @Override
221 public void putToCache(CdmBase cdmEntity) {
222 if (cdmEntity == null){
223 return;
224 }else if (!cdmEntity.isPersited()){
225 putVolatitleEntity(cdmEntity);
226 }else{
227 CdmBase cachedCdmEntity = permanentCache.load(cdmEntity);
228 if(cachedCdmEntity != null) {
229 logger.info("Cdm Entity with id : " + cdmEntity.getId() + " already exists in permanent cache. Ignoring put.");
230 return;
231 }
232 CdmEntityCacheKey<?> key = new CdmEntityCacheKey<>(cdmEntity);
233
234 cachedCdmEntity = getFromCache(key);
235 if(cachedCdmEntity == null) {
236 CdmBase cdmEntityToCache = cdmEntity;
237 CdmBase cachedVolatileEntity = volatileEntitiesMap.get(cdmEntity.getUuid());
238 //if former volatile object became transient now
239 if(cachedVolatileEntity != null) {
240 cachedVolatileEntity.setId(cdmEntity.getId());
241 cdmEntityToCache = cachedVolatileEntity;
242 }
243 putToTransientCache(key, cdmEntityToCache);
244 cdmEntityToCache.initListener();
245 volatileEntitiesMap.remove(cdmEntity.getUuid());
246 if (logger.isDebugEnabled()){logger.debug(" - object of type " + cdmEntityToCache.getClass().getName() + " with id " + cdmEntityToCache.getId() + " put in cache");}
247 return;
248 }else{
249 logger.debug(" - object of type " + cdmEntity.getClass().getName() + " with id " + cdmEntity.getId() + " already exists");
250 }
251 }
252 }
253
254 /**
255 * Puts the entity to the cache for transient entities. If the entity is not transient
256 * but volatile (id = 0) an {@link IllegalArgumentException} is thrown
257 */
258 protected void putToTransientCache(CdmEntityCacheKey<?> key, CdmBase cdmEntityToCache) throws IllegalArgumentException {
259 if (key.getPersistenceId() == 0){
260 throw new IllegalArgumentException("Volatile objects are not allowed in the transient object cache. Use newEntitiesMap instead.");
261 }
262 getCache().put(new Element(key, cdmEntityToCache));
263 }
264
265 private Element getCacheElement(CdmEntityCacheKey<?> key) {
266 return getCache().get(key);
267 }
268
269 public <T extends CdmBase> T getFromCache(CdmEntityCacheKey<T> id) {
270 Element e = getCacheElement(id);
271
272 if (e == null) {
273 return null;
274 } else {
275 @SuppressWarnings("unchecked")
276 T result = (T) e.getObjectValue();
277 return result;
278 }
279 }
280
281 public <T extends CdmBase> T getFromCache(Class<T> clazz, int id) {
282 CdmEntityCacheKey<T> cacheId = generateKey(clazz, id);
283 return getFromCache(cacheId);
284 }
285
286 @Override
287 public <T extends CdmBase> T getFromCache(T cdmBase) {
288 if (!cdmBase.isPersited()){
289 return (T)volatileEntitiesMap.get(cdmBase.getUuid());
290 }else{
291 CdmEntityCacheKey<T> cacheId = generateKey(ProxyUtils.deproxy(cdmBase));
292 // first try this cache
293 T cachedCdmEntity = getFromCache(cacheId);
294
295 if(cachedCdmEntity == null) {
296 // ... then try the permanent cache
297 //TODO also use generics and clazz parameter for getFromCache(uuid)
298 cachedCdmEntity = (T)permanentCache.getFromCache(cdmBase.getUuid());
299 }
300 return cachedCdmEntity;
301 }
302
303 }
304
305 public List<CdmBase> getAllEntities() {
306 List<CdmBase> entities = new ArrayList<>();
307 Map<String, CdmBase> elementsMap = getCache().getAllWithLoader(getCache().getKeys(), null);
308 for (Map.Entry<String, CdmBase> entry : elementsMap.entrySet()) {
309 entities.add(entry.getValue());
310 }
311 return entities;
312 }
313
314 public boolean exists(CdmEntityCacheKey<?> key) {
315 return (getCacheElement(key) != null);
316 }
317
318 public boolean existsAndIsNotNull(CdmEntityCacheKey<?> id) {
319 return getFromCache(id) != null;
320 }
321
322 public void clear() {
323 cache.removeAll();
324 volatileEntitiesMap.clear();
325 }
326
327 @Override
328 public void dispose() {
329 getCacheManager().removeCache(cache.getName());
330 cache.dispose();
331 volatileEntitiesMap.clear();
332 }
333
334 @Override
335 public <T extends CdmBase> T load(T cdmEntity) {
336 return load(cdmEntity, true);
337 }
338
339 @Override
340 public boolean isCachable(CdmBase cdmEntity) {
341 return true;
342 }
343
344 @Override
345 public boolean exists(CdmBase cdmEntity) {
346 return exists(generateKey(cdmEntity));
347 }
348 }