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.lang.ref.ReferenceQueue;
19 import java.lang.ref.SoftReference;
20 import java.util.ArrayList;
21 import java.util.Hashtable;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Properties;
26
27 import org.apache.commons.lang.builder.ToStringBuilder;
28 import org.apache.commons.lang.builder.ToStringStyle;
29 import org.apache.ojb.broker.Identity;
30 import org.apache.ojb.broker.OJBRuntimeException;
31 import org.apache.ojb.broker.PBStateEvent;
32 import org.apache.ojb.broker.PBStateListener;
33 import org.apache.ojb.broker.PersistenceBroker;
34 import org.apache.ojb.broker.util.logging.Logger;
35 import org.apache.ojb.broker.util.logging.LoggerFactory;
36
37
38
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129 public class ObjectCacheDefaultImpl implements ObjectCacheInternal, PBStateListener
130 {
131 private Logger log = LoggerFactory.getLogger(ObjectCacheDefaultImpl.class);
132
133 public static final String TIMEOUT_PROP = "timeout";
134 public static final String AUTOSYNC_PROP = "autoSync";
135 public static final String CACHING_KEY_TYPE_PROP = "cachingKeyType";
136 public static final String SOFT_REFERENCES_PROP = "useSoftReferences";
137
138
139
140 protected static final Map objectTable = new Hashtable();
141 private static final ReferenceQueue queue = new ReferenceQueue();
142
143 private static long hitCount = 0;
144 private static long failCount = 0;
145 private static long gcCount = 0;
146
147 protected PersistenceBroker broker;
148 private List identitiesInWork;
149
150
151
152 private long timeout = 1000 * 60 * 15;
153 private boolean useAutoSync = false;
154
155
156
157
158
159
160
161
162 private int cachingKeyType;
163 private boolean useSoftReferences = true;
164
165 public ObjectCacheDefaultImpl(PersistenceBroker broker, Properties prop)
166 {
167 this.broker = broker;
168 timeout = prop == null ? timeout : (Long.parseLong(prop.getProperty(TIMEOUT_PROP, "" + (60 * 15))) * 1000);
169 useSoftReferences = prop != null && (Boolean.valueOf((prop.getProperty(SOFT_REFERENCES_PROP, "true")).trim())).booleanValue();
170 cachingKeyType = prop == null ? 0 : (Integer.parseInt(prop.getProperty(CACHING_KEY_TYPE_PROP, "0")));
171 useAutoSync = prop != null && (Boolean.valueOf((prop.getProperty(AUTOSYNC_PROP, "false")).trim())).booleanValue();
172 if(useAutoSync)
173 {
174 if(broker != null)
175 {
176
177 broker.addListener(this, true);
178 }
179 else
180 {
181 log.info("Can't enable property '" + AUTOSYNC_PROP + "', because given PB instance is null");
182 }
183 }
184 identitiesInWork = new ArrayList();
185 if(log.isEnabledFor(Logger.INFO))
186 {
187 ToStringBuilder buf = new ToStringBuilder(this);
188 buf.append("timeout", timeout)
189 .append("useSoftReferences", useSoftReferences)
190 .append("cachingKeyType", cachingKeyType)
191 .append("useAutoSync", useAutoSync);
192 log.info("Setup cache: " + buf.toString());
193 }
194 }
195
196
197
198
199 public void clear()
200 {
201
202 objectTable.clear();
203 identitiesInWork.clear();
204 }
205
206 public void doInternalCache(Identity oid, Object obj, int type)
207 {
208
209 if((obj != null))
210 {
211 traceIdentity(oid);
212 synchronized(objectTable)
213 {
214 if(log.isDebugEnabled()) log.debug("Cache object " + oid);
215 objectTable.put(buildKey(oid), buildEntry(obj, oid));
216 }
217 }
218 }
219
220
221
222
223
224
225 public void cache(Identity oid, Object obj)
226 {
227 doInternalCache(oid, obj, ObjectCacheInternal.TYPE_UNKNOWN);
228 }
229
230 public boolean cacheIfNew(Identity oid, Object obj)
231 {
232
233 boolean result = false;
234 Object key = buildKey(oid);
235 if((obj != null))
236 {
237 synchronized(objectTable)
238 {
239 if(!objectTable.containsKey(key))
240 {
241 objectTable.put(key, buildEntry(obj, oid));
242 result = true;
243 }
244 }
245 if(result) traceIdentity(oid);
246 }
247 return result;
248 }
249
250
251
252
253
254 public Object lookup(Identity oid)
255 {
256 processQueue();
257 hitCount++;
258 Object result = null;
259
260 CacheEntry entry = (CacheEntry) objectTable.get(buildKey(oid));
261 if(entry != null)
262 {
263 result = entry.get();
264 if(result == null || entry.getLifetime() < System.currentTimeMillis())
265 {
266
267
268
269
270 gcCount++;
271 remove(oid);
272
273 result = null;
274 }
275 else
276 {
277
278
279
280
281 traceIdentity(oid);
282 if(log.isDebugEnabled()) log.debug("Object match " + oid);
283 }
284 }
285 else
286 {
287 failCount++;
288 }
289 return result;
290 }
291
292
293
294
295 public void remove(Identity oid)
296 {
297
298 if(oid != null)
299 {
300 removeTracedIdentity(oid);
301 objectTable.remove(buildKey(oid));
302 if(log.isDebugEnabled()) log.debug("Remove object " + oid);
303 }
304 }
305
306 public String toString()
307 {
308 ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE);
309 buf.append("Count of cached objects", objectTable.keySet().size());
310 buf.append("Lookup hits", hitCount);
311 buf.append("Failures", failCount);
312 buf.append("Reclaimed", gcCount);
313 return buf.toString();
314 }
315
316 private void traceIdentity(Identity oid)
317 {
318 if(useAutoSync && (broker != null) && broker.isInTransaction())
319 {
320 identitiesInWork.add(oid);
321 }
322 }
323
324 private void removeTracedIdentity(Identity oid)
325 {
326 identitiesInWork.remove(oid);
327 }
328
329 private void synchronizeWithTracedObjects()
330 {
331 Identity oid;
332 log.info("tx was aborted," +
333 " remove " + identitiesInWork.size() + " traced (potentially modified) objects from cache");
334 for(Iterator iterator = identitiesInWork.iterator(); iterator.hasNext();)
335 {
336 oid = (Identity) iterator.next();
337 objectTable.remove(buildKey(oid));
338 }
339 }
340
341 public void beforeRollback(PBStateEvent event)
342 {
343 synchronizeWithTracedObjects();
344 identitiesInWork.clear();
345 }
346
347 public void beforeCommit(PBStateEvent event)
348 {
349
350 }
351
352 public void beforeClose(PBStateEvent event)
353 {
354
355
356
357
358
359 if(!broker.isInTransaction())
360 {
361 identitiesInWork.clear();
362 }
363 }
364
365 public void afterRollback(PBStateEvent event)
366 {
367 }
368
369 public void afterCommit(PBStateEvent event)
370 {
371 identitiesInWork.clear();
372 }
373
374 public void afterBegin(PBStateEvent event)
375 {
376 }
377
378 public void beforeBegin(PBStateEvent event)
379 {
380 }
381
382 public void afterOpen(PBStateEvent event)
383 {
384 }
385
386 private CacheEntry buildEntry(Object obj, Identity oid)
387 {
388 if(useSoftReferences)
389 {
390 return new CacheEntrySoft(obj, oid, queue, timeout);
391 }
392 else
393 {
394 return new CacheEntryHard(obj, oid, timeout);
395 }
396 }
397
398 private void processQueue()
399 {
400 CacheEntry sv;
401 while((sv = (CacheEntry) queue.poll()) != null)
402 {
403 removeTracedIdentity(sv.getOid());
404 objectTable.remove(buildKey(sv.getOid()));
405 }
406 }
407
408 private Object buildKey(Identity oid)
409 {
410 Object key;
411 switch(cachingKeyType)
412 {
413 case 0:
414 key = oid;
415 break;
416 case 1:
417 key = new OrderedTuple(oid, broker.getPBKey().getAlias());
418 break;
419 case 2:
420
421
422
423
424
425 key = new OrderedTuple(oid,
426 new Integer(broker.getDescriptorRepository().hashCode()));
427 break;
428 case 3:
429 key = new OrderedTuple(oid, broker.getPBKey().getAlias(),
430 new Integer(broker.getDescriptorRepository().hashCode()));
431 break;
432 default:
433 throw new OJBRuntimeException("Unexpected error, 'cacheType =" + cachingKeyType + "' was not supported");
434 }
435 return key;
436 }
437
438
439
440
441
442
443
444
445
446
447
448
449 static final class OrderedTuple
450 {
451 private static int[] multipliers =
452 new int[]{13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 51};
453
454 private Object[] elements;
455 private int hashCode;
456
457 public OrderedTuple(Object element)
458 {
459 elements = new Object[1];
460 elements[0] = element;
461 hashCode = calcHashCode();
462 }
463
464 public OrderedTuple(Object element1, Object element2)
465 {
466 elements = new Object[2];
467 elements[0] = element1;
468 elements[1] = element2;
469 hashCode = calcHashCode();
470 }
471
472 public OrderedTuple(Object element1, Object element2, Object element3)
473 {
474 elements = new Object[3];
475 elements[0] = element1;
476 elements[1] = element2;
477 elements[2] = element3;
478 hashCode = calcHashCode();
479 }
480
481 public OrderedTuple(Object[] elements)
482 {
483 this.elements = elements;
484 this.hashCode = calcHashCode();
485 }
486
487 private int calcHashCode()
488 {
489 int code = 7;
490 for(int i = 0; i < elements.length; i++)
491 {
492 int m = i % multipliers.length;
493 code += elements[i].hashCode() * multipliers[m];
494 }
495 return code;
496 }
497
498 public boolean equals(Object obj)
499 {
500 if(!(obj instanceof OrderedTuple))
501 {
502 return false;
503 }
504 else
505 {
506 OrderedTuple other = (OrderedTuple) obj;
507 if(this.hashCode != other.hashCode)
508 {
509 return false;
510 }
511 else if(this.elements.length != other.elements.length)
512 {
513 return false;
514 }
515 else
516 {
517 for(int i = 0; i < elements.length; i++)
518 {
519 if(!this.elements[i].equals(other.elements[i]))
520 {
521 return false;
522 }
523 }
524 return true;
525 }
526 }
527 }
528
529 public int hashCode()
530 {
531 return hashCode;
532 }
533
534 public String toString()
535 {
536 StringBuffer s = new StringBuffer();
537 s.append('{');
538 for(int i = 0; i < elements.length; i++)
539 {
540 s.append(elements[i]).append('#').append(elements[i].hashCode()).append(',');
541 }
542 s.setCharAt(s.length() - 1, '}');
543 s.append("#").append(hashCode);
544 return s.toString();
545 }
546 }
547
548
549
550
551 interface CacheEntry
552 {
553 Object get();
554 Identity getOid();
555 long getLifetime();
556 }
557
558 final static class CacheEntrySoft extends SoftReference implements CacheEntry
559 {
560 private final long lifetime;
561 private final Identity oid;
562
563 CacheEntrySoft(Object object, final Identity k, final ReferenceQueue q, long timeout)
564 {
565 super(object, q);
566 oid = k;
567
568 if(timeout < 0)
569 {
570 lifetime = Long.MAX_VALUE;
571 }
572 else
573 {
574 lifetime = System.currentTimeMillis() + timeout;
575 }
576 }
577
578 public Identity getOid()
579 {
580 return oid;
581 }
582
583 public long getLifetime()
584 {
585 return lifetime;
586 }
587 }
588
589 final static class CacheEntryHard implements CacheEntry
590 {
591 private final long lifetime;
592 private final Identity oid;
593 private Object obj;
594
595 CacheEntryHard(Object object, final Identity k, long timeout)
596 {
597 obj = object;
598 oid = k;
599
600 if(timeout < 0)
601 {
602 lifetime = Long.MAX_VALUE;
603 }
604 else
605 {
606 lifetime = System.currentTimeMillis() + timeout;
607 }
608 }
609
610 public Object get()
611 {
612 return obj;
613 }
614
615 public Identity getOid()
616 {
617 return oid;
618 }
619
620 public long getLifetime()
621 {
622 return lifetime;
623 }
624 }
625 }