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 | |
} |