1 package org.apache.ojb.broker.cache;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import java.io.Serializable;
19 import java.lang.ref.ReferenceQueue;
20 import java.lang.ref.SoftReference;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.Properties;
24
25 import org.apache.ojb.broker.Identity;
26 import org.apache.ojb.broker.PBStateEvent;
27 import org.apache.ojb.broker.PBStateListener;
28 import org.apache.ojb.broker.PersistenceBroker;
29 import org.apache.ojb.broker.core.DelegatingPersistenceBroker;
30 import org.apache.ojb.broker.core.PersistenceBrokerImpl;
31 import org.apache.ojb.broker.core.proxy.ProxyHelper;
32 import org.apache.ojb.broker.metadata.ClassDescriptor;
33 import org.apache.ojb.broker.metadata.FieldDescriptor;
34 import org.apache.ojb.broker.metadata.MetadataException;
35 import org.apache.ojb.broker.util.ClassHelper;
36 import org.apache.ojb.broker.util.logging.Logger;
37 import org.apache.ojb.broker.util.logging.LoggerFactory;
38 import org.apache.commons.lang.builder.ToStringBuilder;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public class ObjectCacheTwoLevelImpl implements ObjectCacheInternal, PBStateListener
108 {
109 private Logger log = LoggerFactory.getLogger(ObjectCacheTwoLevelImpl.class);
110
111 public static final String APPLICATION_CACHE_PROP = "applicationCache";
112 public static final String COPY_STRATEGY_PROP = "copyStrategy";
113 public static final String FORCE_PROXIES = "forceProxies";
114 private static final String DEF_COPY_STRATEGY = ObjectCacheTwoLevelImpl.CopyStrategyImpl.class.getName();
115 private static final String DEF_APP_CACHE = ObjectCacheDefaultImpl.class.getName();
116
117 private HashMap sessionCache;
118
119 private int invokeCounter;
120 private ReferenceQueue queue = new ReferenceQueue();
121 private ObjectCacheInternal applicationCache;
122 private CopyStrategy copyStrategy;
123 private PersistenceBrokerImpl broker;
124 private boolean forceProxies = false;
125
126 public ObjectCacheTwoLevelImpl(final PersistenceBroker broker, Properties prop)
127 {
128
129 if(broker instanceof PersistenceBrokerImpl)
130 {
131 this.broker = (PersistenceBrokerImpl) broker;
132 }
133 else if(broker instanceof DelegatingPersistenceBroker)
134 {
135 this.broker = (PersistenceBrokerImpl) ((DelegatingPersistenceBroker) broker).getInnermostDelegate();
136 }
137 else
138 {
139 throw new RuntimeCacheException("Can't initialize two level cache, expect instance of"
140 + PersistenceBrokerImpl.class + " or of " + DelegatingPersistenceBroker.class
141 + " to setup application cache, but was " + broker);
142 }
143 this.sessionCache = new HashMap(100);
144
145 setupApplicationCache(this.broker, prop);
146
147 broker.addListener(this, true);
148 }
149
150
151
152
153
154 public PersistenceBrokerImpl getBroker()
155 {
156 return broker;
157 }
158
159 private void setupApplicationCache(PersistenceBrokerImpl broker, Properties prop)
160 {
161 if(log.isDebugEnabled()) log.debug("Start setup application cache for broker " + broker);
162 if(prop == null)
163 {
164 prop = new Properties();
165 }
166 String copyStrategyName = prop.getProperty(COPY_STRATEGY_PROP, DEF_COPY_STRATEGY).trim();
167 if(copyStrategyName.length() == 0)
168 {
169 copyStrategyName = DEF_COPY_STRATEGY;
170 }
171 String applicationCacheName = prop.getProperty(APPLICATION_CACHE_PROP, DEF_APP_CACHE).trim();
172 if(applicationCacheName.length() == 0)
173 {
174 applicationCacheName = DEF_APP_CACHE;
175 }
176
177 String forceProxyValue = prop.getProperty(FORCE_PROXIES, "false").trim();
178 forceProxies = Boolean.valueOf(forceProxyValue).booleanValue();
179
180 if (forceProxies && broker.getProxyFactory().interfaceRequiredForProxyGeneration()){
181 log.warn("'" + FORCE_PROXIES + "' is set to true, however a ProxyFactory implementation " +
182 "[" + broker.getProxyFactory().getClass().getName() +"] " +
183 " that requires persistent objects to implement an inteface is being used. Please ensure " +
184 "that all persistent objects implement an interface, or change the ProxyFactory setting to a dynamic " +
185 "proxy generator (like ProxyFactoryCGLIBImpl).");
186 }
187
188 Class[] type = new Class[]{PersistenceBroker.class, Properties.class};
189 Object[] objects = new Object[]{broker, prop};
190 try
191 {
192 this.copyStrategy = (CopyStrategy) ClassHelper.newInstance(copyStrategyName);
193 Class target = ClassHelper.getClass(applicationCacheName);
194 if(target.equals(ObjectCacheDefaultImpl.class))
195 {
196
197 prop.setProperty(ObjectCacheDefaultImpl.AUTOSYNC_PROP, "false");
198 }
199 ObjectCache temp = (ObjectCache) ClassHelper.newInstance(target, type, objects);
200 if(!(temp instanceof ObjectCacheInternal))
201 {
202 log.warn("Specified application cache class doesn't implement '" + ObjectCacheInternal.class.getName()
203 + "'. For best interaction only specify caches implementing the internal object cache interface.");
204 temp = new CacheDistributor.ObjectCacheInternalWrapper(temp);
205 }
206 this.applicationCache = (ObjectCacheInternal) temp;
207 }
208 catch(Exception e)
209 {
210 throw new MetadataException("Can't setup application cache. Specified application cache was '"
211 + applicationCacheName + "', copy strategy was '" + copyStrategyName + "'", e);
212 }
213 if(log.isEnabledFor(Logger.INFO))
214 {
215 ToStringBuilder buf = new ToStringBuilder(this);
216 buf.append("copyStrategy", copyStrategyName)
217 .append("applicationCache", applicationCacheName);
218 log.info("Setup cache: " + buf.toString());
219 }
220 }
221
222
223
224
225
226
227 public ObjectCacheInternal getApplicationCache()
228 {
229 return applicationCache;
230 }
231
232 private Object lookupFromApplicationCache(Identity oid)
233 {
234 Object result = null;
235 Object obj = getApplicationCache().lookup(oid);
236 if(obj != null)
237 {
238 result = copyStrategy.read(broker, obj);
239 }
240 return result;
241 }
242
243 private boolean putToApplicationCache(Identity oid, Object obj, boolean cacheIfNew)
244 {
245
246
247
248
249 Object oldTarget = null;
250 if(!cacheIfNew)
251 {
252 oldTarget = getApplicationCache().lookup(oid);
253 }
254 Object target = copyStrategy.write(broker, obj, oldTarget);
255 if(cacheIfNew)
256 {
257 return getApplicationCache().cacheIfNew(oid, target);
258 }
259 else
260 {
261 getApplicationCache().cache(oid, target);
262 return false;
263 }
264 }
265
266
267
268
269
270 public void resetSessionCache()
271 {
272 sessionCache.clear();
273 invokeCounter = 0;
274 }
275
276
277
278
279
280 private void pushToApplicationCache(int typeToProcess, int typeAfterProcess)
281 {
282 for(Iterator iter = sessionCache.values().iterator(); iter.hasNext();)
283 {
284 CacheEntry entry = (CacheEntry) iter.next();
285
286 Object result = entry.get();
287 if(result == null)
288 {
289 if(log.isDebugEnabled())
290 log.debug("Object in session cache was gc, nothing to push to application cache");
291 }
292 else
293 {
294
295 if(entry.type == typeToProcess)
296 {
297 if(log.isDebugEnabled())
298 {
299 log.debug("Move obj from session cache --> application cache : " + entry.oid);
300 }
301
302
303
304
305 if(ProxyHelper.isMaterialized(result))
306 {
307 putToApplicationCache(entry.oid, ProxyHelper.getRealObject(result), false);
308
309 entry.type = typeAfterProcess;
310 }
311 }
312 }
313 }
314 }
315
316
317
318
319
320
321
322 public void doInternalCache(Identity oid, Object obj, int type)
323 {
324 processQueue();
325
326 if(type == TYPE_NEW_MATERIALIZED)
327 {
328 boolean result = putToApplicationCache(oid, obj, true);
329 CacheEntry entry = new CacheEntry(oid, obj, TYPE_CACHED_READ, queue);
330 if(result)
331 {
332
333
334 putToSessionCache(oid, entry, false);
335 }
336 else
337 {
338
339
340 putToSessionCache(oid, entry, true);
341 if(log.isDebugEnabled())
342 {
343 log.debug("The 'new' materialized object was already in cache," +
344 " will not push it to application cache: " + oid);
345 }
346 }
347 }
348 else
349 {
350
351
352 CacheEntry entry = new CacheEntry(oid, obj, type, queue);
353 putToSessionCache(oid, entry, false);
354 }
355 }
356
357
358
359
360
361
362 public Object lookup(Identity oid)
363 {
364 Object result = null;
365
366 CacheEntry entry = (CacheEntry) sessionCache.get(oid);
367 if(entry != null)
368 {
369 result = entry.get();
370 }
371 if(result == null)
372 {
373 result = lookupFromApplicationCache(oid);
374
375
376 if(result != null)
377 {
378 doInternalCache(oid, result, TYPE_CACHED_READ);
379 materializeFullObject(result);
380 if(log.isDebugEnabled()) log.debug("Materialized object from second level cache: " + oid);
381 }
382 }
383 if(result != null && log.isDebugEnabled())
384 {
385 log.debug("Match for: " + oid);
386 }
387 return result;
388 }
389
390
391
392
393
394
395
396
397
398
399
400
401
402 public void materializeFullObject(Object target)
403 {
404 ClassDescriptor cld = broker.getClassDescriptor(target.getClass());
405
406 final boolean forced = false;
407 if (forceProxies){
408 broker.getReferenceBroker().retrieveProxyReferences(target, cld, forced);
409 broker.getReferenceBroker().retrieveProxyCollections(target, cld, forced);
410 }else{
411 broker.getReferenceBroker().retrieveReferences(target, cld, forced);
412 broker.getReferenceBroker().retrieveCollections(target, cld, forced);
413 }
414 }
415
416
417
418
419 public void remove(Identity oid)
420 {
421 if(log.isDebugEnabled()) log.debug("Remove object " + oid);
422 sessionCache.remove(oid);
423 getApplicationCache().remove(oid);
424 }
425
426
427
428
429 public void clear()
430 {
431 sessionCache.clear();
432 getApplicationCache().clear();
433 }
434
435
436
437
438 public void cache(Identity oid, Object obj)
439 {
440 doInternalCache(oid, obj, TYPE_UNKNOWN);
441 }
442
443 public boolean cacheIfNew(Identity oid, Object obj)
444 {
445 boolean result = putToApplicationCache(oid, obj, true);
446 if(result)
447 {
448 CacheEntry entry = new CacheEntry(oid, obj, TYPE_CACHED_READ, queue);
449 putToSessionCache(oid, entry, true);
450 }
451 return result;
452 }
453
454
455
456
457
458
459
460
461 private void putToSessionCache(Identity oid, CacheEntry entry, boolean onlyIfNew)
462 {
463 if(onlyIfNew)
464 {
465
466 if(!sessionCache.containsKey(oid)) sessionCache.put(oid, entry);
467 }
468 else
469 {
470 sessionCache.put(oid, entry);
471 }
472 }
473
474
475
476
477
478 private void processQueue()
479 {
480 CacheEntry sv;
481 while((sv = (CacheEntry) queue.poll()) != null)
482 {
483 sessionCache.remove(sv.oid);
484 }
485 }
486
487
488
489
490
491
492
493
494
495 public void afterCommit(PBStateEvent event)
496 {
497 if(log.isDebugEnabled()) log.debug("afterCommit() call, push objects to application cache");
498 if(invokeCounter != 0)
499 {
500 log.error("** Please check method calls of ObjectCacheTwoLevelImpl#enableMaterialization and" +
501 " ObjectCacheTwoLevelImpl#disableMaterialization, number of calls have to be equals **");
502 }
503 try
504 {
505
506 pushToApplicationCache(TYPE_WRITE, TYPE_CACHED_READ);
507 }
508 finally
509 {
510 resetSessionCache();
511 }
512 }
513
514
515
516
517
518 public void beforeClose(PBStateEvent event)
519 {
520
521
522
523
524
525
526
527
528
529
530
531 if(!broker.isInTransaction())
532 {
533 if(log.isDebugEnabled()) log.debug("Clearing the session cache");
534 resetSessionCache();
535 }
536 }
537
538
539
540
541 public void beforeRollback(PBStateEvent event)
542 {
543 if(log.isDebugEnabled()) log.debug("beforeRollback()");
544 resetSessionCache();
545 }
546
547 public void afterOpen(PBStateEvent event)
548 {
549 }
550
551 public void beforeBegin(PBStateEvent event)
552 {
553 }
554
555 public void afterBegin(PBStateEvent event)
556 {
557 }
558
559 public void beforeCommit(PBStateEvent event)
560 {
561 }
562
563 public void afterRollback(PBStateEvent event)
564 {
565 }
566
567
568
569
570
571
572
573
574
575
576 static final class CacheEntry extends SoftReference implements Serializable
577 {
578 private int type;
579 private Identity oid;
580
581 public CacheEntry(Identity oid, Object obj, int type, final ReferenceQueue q)
582 {
583 super(obj, q);
584 this.oid = oid;
585 this.type = type;
586 }
587 }
588
589
590 public interface CopyStrategy
591 {
592
593
594
595
596
597
598
599
600 public Object read(PersistenceBroker broker, Object obj);
601
602
603
604
605
606
607
608
609
610 public Object write(PersistenceBroker broker, Object obj, Object oldObject);
611 }
612
613 public static class CopyStrategyImpl implements CopyStrategy
614 {
615 static final String CLASS_NAME_STR = "ojbClassName11";
616
617 public CopyStrategyImpl()
618 {
619 }
620
621 public Object read(PersistenceBroker broker, Object obj)
622 {
623 HashMap source = (HashMap) obj;
624 String className = (String) source.get(CLASS_NAME_STR);
625 ClassDescriptor cld = broker.getDescriptorRepository().getDescriptorFor(className);
626 Object target = ClassHelper.buildNewObjectInstance(cld);
627
628 FieldDescriptor[] flds = cld.getFieldDescriptor(true);
629 FieldDescriptor fld;
630 int length = flds.length;
631 for(int i = 0; i < length; i++)
632 {
633 fld = flds[i];
634
635 Object value = source.get(fld.getPersistentField().getName());
636
637 if(value != null) value = fld.getJdbcType().getFieldType().copy(value);
638
639
640 value = fld.getFieldConversion().sqlToJava(value);
641
642 fld.getPersistentField().set(target, value);
643 }
644 return target;
645 }
646
647 public Object write(PersistenceBroker broker, Object obj, Object oldObject)
648 {
649 ClassDescriptor cld = broker.getClassDescriptor(obj.getClass());
650
651 HashMap target = oldObject != null ? (HashMap) oldObject : new HashMap();
652
653 FieldDescriptor[] flds = cld.getFieldDescriptor(true);
654 FieldDescriptor fld;
655 int length = flds.length;
656 for(int i = 0; i < length; i++)
657 {
658 fld = flds[i];
659
660 Object value = fld.getPersistentField().get(obj);
661
662 value = fld.getFieldConversion().javaToSql(value);
663
664 value = fld.getJdbcType().getFieldType().copy(value);
665 target.put(fld.getPersistentField().getName(), value);
666 }
667 target.put(CLASS_NAME_STR, obj.getClass().getName());
668 return target;
669 }
670 }
671 }