KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > cmp > jdbc > ReadAheadCache


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.ejb.plugins.cmp.jdbc;
23
24 import org.jboss.ejb.EntityEnterpriseContext;
25 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
26 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
27 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
28 import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
29 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
30 import org.jboss.logging.Logger;
31 import org.jboss.tm.TransactionLocal;
32
33 import javax.transaction.Transaction JavaDoc;
34 import javax.transaction.SystemException JavaDoc;
35 import java.lang.ref.SoftReference JavaDoc;
36 import java.util.Collection JavaDoc;
37 import java.util.Collections JavaDoc;
38 import java.util.HashMap JavaDoc;
39 import java.util.HashSet JavaDoc;
40 import java.util.Iterator JavaDoc;
41 import java.util.LinkedList JavaDoc;
42 import java.util.List JavaDoc;
43 import java.util.Map JavaDoc;
44
45
46 /**
47  * ReadAheadCache stores all of the data readahead for an entity.
48  * Data is stored in the JDBCStoreManager entity tx data map on a per entity
49  * basis. The read ahead data for each entity is stored with a soft reference.
50  *
51  * @author <a HREF="mailto:dain@daingroup.com">Dain Sundstrom</a>
52  * @version $Revision: 41544 $
53  */

54 public final class ReadAheadCache
55 {
56    /**
57     * To simplify null values handling in the preloaded data pool we use
58     * this value instead of 'null'
59     */

60    private static final Object JavaDoc NULL_VALUE = new Object JavaDoc();
61
62    private final JDBCStoreManager manager;
63    private final Logger log;
64
65    private final TransactionLocal listMapTxLocal = new TransactionLocal()
66    {
67       protected Object JavaDoc initialValue()
68       {
69          return new HashMap JavaDoc();
70       }
71
72       public Transaction JavaDoc getTransaction()
73       {
74          try
75          {
76             return transactionManager.getTransaction();
77          }
78          catch(SystemException JavaDoc e)
79          {
80             throw new IllegalStateException JavaDoc("An error occured while getting the " +
81                "transaction associated with the current thread: " + e);
82          }
83       }
84    };
85
86    private ListCache listCache;
87    private int listCacheMax;
88
89    public ReadAheadCache(JDBCStoreManager manager)
90    {
91       this.manager = manager;
92
93       // Create the Log
94
log = Logger.getLogger(
95          this.getClass().getName() +
96          "." +
97          manager.getMetaData().getName());
98    }
99
100    public void create()
101    {
102       // Create the list cache
103
listCacheMax = ((JDBCEntityBridge)manager.getEntityBridge()).getListCacheMax();
104       listCache = new ListCache(listCacheMax);
105    }
106
107    public void start()
108    {
109    }
110
111    public void stop()
112    {
113       listCache.clear();
114    }
115
116    public void destroy()
117    {
118       listCache = null;
119    }
120
121    public void addFinderResults(List JavaDoc results, JDBCReadAheadMetaData readahead)
122    {
123       if(listCacheMax == 0 || results.size() < 2)
124       {
125          // nothing to see here... move along
126
return;
127       }
128
129       Map JavaDoc listMap = getListMap();
130       if(listMap == null)
131       {
132          // no active transaction
133
return;
134       }
135
136       if(log.isTraceEnabled())
137       {
138          log.trace("Add finder results:" +
139             " entity=" + manager.getEntityBridge().getEntityName() +
140             " results=" + results +
141             " readahead=" + readahead);
142       }
143
144       // add the finder to the LRU list
145
if(!readahead.isNone())
146       {
147          listCache.add(results);
148       }
149
150       //
151
// Create a map between the entity primary keys and the list.
152
// The primary key will point to the last list added that contained the
153
// primary key.
154
//
155
HashSet JavaDoc dereferencedResults = new HashSet JavaDoc();
156       Iterator JavaDoc iter = results.iterator();
157       for(int i = 0; iter.hasNext(); i++)
158       {
159          Object JavaDoc pk = iter.next();
160
161          // create the new entry object
162
EntityMapEntry entry;
163          if(readahead.isNone())
164          {
165             entry = new EntityMapEntry(0, Collections.singletonList(pk), readahead);
166          }
167          else
168          {
169             entry = new EntityMapEntry(i, results, readahead);
170          }
171
172          // Keep track of the results that have been dereferenced. Later we
173
// all results from the list cache that are no longer referenced.
174
EntityMapEntry oldInfo = (EntityMapEntry) listMap.put(pk, entry);
175          if(oldInfo != null)
176          {
177             dereferencedResults.add(oldInfo.results);
178          }
179       }
180
181       //
182
// Now we remove all lists from the list cache that are no longer
183
// referenced in the list map.
184
//
185

186       // if we don't have any dereferenced results at this point we are done
187
if(dereferencedResults.isEmpty())
188       {
189          return;
190       }
191
192       //
193
// Go through the dereferenced results set and look at the PKs for each
194
// dereferenced list. If you find one key that references the
195
// dereferenced list, remove it from the dereferenced results set and
196
// move on to the next dereferenced results.
197
//
198
iter = dereferencedResults.iterator();
199       while(iter.hasNext())
200       {
201          List JavaDoc dereferencedList = (List JavaDoc) iter.next();
202
203          boolean listHasReference = false;
204          Iterator JavaDoc iter2 = dereferencedList.iterator();
205          while(!listHasReference &&
206             iter2.hasNext())
207          {
208             EntityMapEntry entry = (EntityMapEntry) listMap.get(iter2.next());
209             if(entry != null && entry.results == dereferencedList)
210             {
211                listHasReference = true;
212             }
213          }
214
215          if(listHasReference)
216          {
217             // this list does not have any references
218
iter.remove();
219          }
220       }
221
222       // if we don't have any dereferenced results at this point we are done
223
if(dereferencedResults.isEmpty())
224       {
225          return;
226       }
227
228       // remove all results from the cache that are no longer referenced
229
iter = dereferencedResults.iterator();
230       while(iter.hasNext())
231       {
232          List JavaDoc list = (List JavaDoc) iter.next();
233          if(log.isTraceEnabled())
234          {
235             log.trace("Removing dereferenced results: " + list);
236          }
237          listCache.remove(list);
238       }
239    }
240
241    private void removeFinderResult(List JavaDoc results)
242    {
243       Map JavaDoc listMap = getListMap();
244       if(listMap == null)
245       {
246          // no active transaction
247
return;
248       }
249
250       // remove the list from the list cache
251
listCache.remove(results);
252
253       // remove all primary keys from the listMap that reference this list
254
if(!listMap.isEmpty())
255       {
256          Iterator JavaDoc iter = listMap.values().iterator();
257          while(iter.hasNext())
258          {
259             EntityMapEntry entry = (EntityMapEntry) iter.next();
260
261             // use == because only identity matters here
262
if(entry.results == results)
263             {
264                iter.remove();
265             }
266          }
267       }
268    }
269
270    public EntityReadAheadInfo getEntityReadAheadInfo(Object JavaDoc pk)
271    {
272       Map JavaDoc listMap = getListMap();
273       if(listMap == null)
274       {
275          // no active transaction
276
return new EntityReadAheadInfo(Collections.singletonList(pk));
277       }
278
279       EntityMapEntry entry = (EntityMapEntry) getListMap().get(pk);
280       if(entry != null)
281       {
282          // we're using these results so promote it to the head of the
283
// LRU list
284
if(!entry.readahead.isNone())
285          {
286             listCache.promote(entry.results);
287          }
288
289          // get the readahead metadata
290
JDBCReadAheadMetaData readahead = entry.readahead;
291          if(readahead == null)
292          {
293             readahead = manager.getMetaData().getReadAhead();
294          }
295
296          int from = entry.index;
297          int to = Math.min(entry.results.size(), entry.index + readahead.getPageSize());
298          List JavaDoc loadKeys = entry.results.subList(from, to);
299
300          return new EntityReadAheadInfo(loadKeys, readahead);
301       }
302       else
303       {
304          return new EntityReadAheadInfo(Collections.singletonList(pk));
305       }
306    }
307
308    /**
309     * Loads all of the preloaded data for the ctx into it.
310     * @param ctx the context that will be loaded
311     * @return true if at least one field was loaded.
312     */

313    public boolean load(EntityEnterpriseContext ctx)
314    {
315       if(log.isTraceEnabled())
316       {
317          log.trace("load data:" +
318             " entity=" + manager.getEntityBridge().getEntityName() +
319             " pk=" + ctx.getId());
320       }
321
322       // get the preload data map
323
Map JavaDoc preloadDataMap = getPreloadDataMap(ctx.getId(), false);
324       if(preloadDataMap == null || preloadDataMap.isEmpty())
325       {
326          // no preloaded data for this entity
327
if(log.isTraceEnabled())
328          {
329             log.trace("No preload data found:" +
330                " entity=" + manager.getEntityBridge().getEntityName() +
331                " pk=" + ctx.getId());
332          }
333          return false;
334       }
335
336       boolean cleanReadAhead = manager.getMetaData().isCleanReadAheadOnLoad();
337
338       boolean loaded = false;
339       JDBCCMRFieldBridge onlyOneSingleValuedCMR = null;
340
341       // iterate over the keys in the preloaded map
342
Iterator JavaDoc iter = preloadDataMap.entrySet().iterator();
343       while(iter.hasNext())
344       {
345          Map.Entry JavaDoc entry = (Map.Entry JavaDoc) iter.next();
346          Object JavaDoc field = entry.getKey();
347
348          // get the value that was preloaded for this field
349
Object JavaDoc value = entry.getValue();
350
351          // if we didn't get a value something is seriously hosed
352
if(value == null)
353          {
354             throw new IllegalStateException JavaDoc("Preloaded value not found");
355          }
356
357          if(cleanReadAhead)
358          {
359             // remove this value from the preload cache as it is about to be loaded
360
iter.remove();
361          }
362
363          // check for null value standin
364
if(value == NULL_VALUE)
365          {
366             value = null;
367          }
368
369          if(field instanceof JDBCCMPFieldBridge)
370          {
371             JDBCCMPFieldBridge cmpField = (JDBCCMPFieldBridge) field;
372
373             if(!cmpField.isLoaded(ctx))
374             {
375                if(log.isTraceEnabled())
376                {
377                   log.trace("Preloading data:" +
378                      " entity=" + manager.getEntityBridge().getEntityName() +
379                      " pk=" + ctx.getId() +
380                      " cmpField=" + cmpField.getFieldName());
381                }
382
383                // set the value
384
cmpField.setInstanceValue(ctx, value);
385
386                // mark this field clean as it's value was just loaded
387
cmpField.setClean(ctx);
388
389                loaded = true;
390             }
391             else
392             {
393                if(log.isTraceEnabled())
394                {
395                   log.trace("CMPField already loaded:" +
396                      " entity=" + manager.getEntityBridge().getEntityName() +
397                      " pk=" + ctx.getId() +
398                      " cmpField=" + cmpField.getFieldName());
399                }
400             }
401          }
402          else if(field instanceof JDBCCMRFieldBridge)
403          {
404             JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) field;
405
406             if(!cmrField.isLoaded(ctx))
407             {
408                if(log.isTraceEnabled())
409                {
410                   log.trace("Preloading data:" +
411                      " entity=" + manager.getEntityBridge().getEntityName() +
412                      " pk=" + ctx.getId() +
413                      " cmrField=" + cmrField.getFieldName());
414                }
415
416                // set the value
417
cmrField.load(ctx, (List JavaDoc) value);
418
419                // add the loaded list to the related entity's readahead cache
420
JDBCStoreManager relatedManager = (JDBCStoreManager) cmrField.getRelatedCMRField().getManager();
421                ReadAheadCache relatedReadAheadCache =
422                   relatedManager.getReadAheadCache();
423                relatedReadAheadCache.addFinderResults(
424                   (List JavaDoc) value, cmrField.getReadAhead());
425
426                if(!loaded)
427                {
428                   // this is a hack to fix on-load read-ahead for 1:m relationships
429
if(cmrField.isSingleValued() && onlyOneSingleValuedCMR == null)
430                   {
431                      onlyOneSingleValuedCMR = cmrField;
432                   }
433                   else
434                   {
435                      loaded = true;
436                   }
437                }
438             }
439             else
440             {
441                if(log.isTraceEnabled())
442                {
443                   log.trace("CMRField already loaded:" +
444                      " entity=" + manager.getEntityBridge().getEntityName() +
445                      " pk=" + ctx.getId() +
446                      " cmrField=" + cmrField.getFieldName());
447                }
448             }
449          }
450       }
451
452       if(cleanReadAhead)
453       {
454          // remove all preload data map as all of the data has been loaded
455
manager.removeEntityTxData(new PreloadKey(ctx.getId()));
456       }
457
458       return loaded;
459    }
460
461    /**
462     * Returns the cached value of a CMR field or null if nothing was cached for this field.
463     * @param pk primary key.
464     * @param cmrField the field to get the cached value for.
465     * @return cached value for the <code>cmrField</code> or null if no value cached.
466     */

467    public Collection JavaDoc getCachedCMRValue(Object JavaDoc pk, JDBCCMRFieldBridge cmrField)
468    {
469       Map JavaDoc preloadDataMap = getPreloadDataMap(pk, true);
470       return (Collection JavaDoc)preloadDataMap.get(cmrField);
471    }
472
473    /**
474     * Add preloaded data for an entity within the scope of a transaction
475     */

476    public void addPreloadData(Object JavaDoc pk,
477                               JDBCFieldBridge field,
478                               Object JavaDoc fieldValue)
479    {
480       if(field instanceof JDBCCMRFieldBridge)
481       {
482          if(fieldValue == null)
483          {
484             fieldValue = Collections.EMPTY_LIST;
485          }
486          else if(!(fieldValue instanceof Collection JavaDoc))
487          {
488             fieldValue = Collections.singletonList(fieldValue);
489          }
490       }
491
492       if(log.isTraceEnabled())
493       {
494          log.trace("Add preload data:" +
495             " entity=" + manager.getEntityBridge().getEntityName() +
496             " pk=" + pk +
497             " field=" + field.getFieldName());
498       }
499
500       // convert null values to a null value standing object
501
if(fieldValue == null)
502       {
503          fieldValue = NULL_VALUE;
504       }
505
506       // store the preloaded data
507
Map JavaDoc preloadDataMap = getPreloadDataMap(pk, true);
508       Object JavaDoc overriden = preloadDataMap.put(field, fieldValue);
509
510       if(log.isTraceEnabled() && overriden != null)
511       {
512          log.trace(
513             "Overriding cached value " + overriden +
514             " with " + (fieldValue == NULL_VALUE ? null : fieldValue) +
515             ". pk=" + pk +
516             ", field=" + field.getFieldName()
517          );
518       }
519    }
520
521    public void removeCachedData(Object JavaDoc primaryKey)
522    {
523       if(log.isTraceEnabled())
524       {
525          log.trace("Removing cached data for " + primaryKey);
526       }
527
528       Map JavaDoc listMap = getListMap();
529       if(listMap == null)
530       {
531          // no active tx
532
return;
533       }
534
535       // remove the preloaded data
536
manager.removeEntityTxData(new PreloadKey(primaryKey));
537
538       // if the entity didn't have readahead entry, or it was read-ahead
539
// none; return
540
EntityMapEntry oldInfo = (EntityMapEntry) listMap.remove(primaryKey);
541       if(oldInfo == null || oldInfo.readahead.isNone())
542       {
543          return;
544       }
545
546       // check to see if the dereferenced finder result is still referenced
547
Iterator JavaDoc iter = listMap.values().iterator();
548       while(iter.hasNext())
549       {
550          EntityMapEntry entry = (EntityMapEntry) iter.next();
551
552          // use == because only identity matters here
553
if(entry.results == oldInfo.results)
554          {
555             // ok it is still referenced
556
return;
557          }
558       }
559
560       // a reference to the old finder set was not found so remove it
561
if(log.isTraceEnabled())
562       {
563          log.trace("Removing dereferenced finder results: " +
564             oldInfo.results);
565       }
566       listCache.remove(oldInfo.results);
567    }
568
569    /**
570     * Gets the map of preloaded data.
571     * @param entityPrimaryKey the primary key of the entity
572     * @param create should a new preload data map be created if one is not found
573     * @return the preload data map for null if one is not found
574     */

575    public Map JavaDoc getPreloadDataMap(Object JavaDoc entityPrimaryKey, boolean create)
576    {
577       //
578
// Be careful in this code. A soft reference may be cleared at any time,
579
// so don't check if a reference has a value and then get that value.
580
// Instead get the value and then check if it is null.
581
//
582

583       // create a preload key for the entity
584
PreloadKey preloadKey = new PreloadKey(entityPrimaryKey);
585
586       // get the soft reference to the preload data map
587
SoftReference JavaDoc ref = (SoftReference JavaDoc) manager.getEntityTxData(preloadKey);
588
589       // did we get a reference
590
if(ref != null)
591       {
592          // get the map from the reference
593
Map JavaDoc preloadDataMap = (Map JavaDoc) ref.get();
594
595          // did we actually get a map? (will be null if it has been GC'd)
596
if(preloadDataMap != null)
597          {
598             return preloadDataMap;
599          }
600       }
601
602       //
603
// at this point we did not get an existing value
604
//
605
// if we got a dead reference remove it
606
if(ref != null)
607       {
608          //log.info(manager.getMetaData().getName() + " was GC'd from read ahead");
609
manager.removeEntityTxData(preloadKey);
610       }
611
612       // if we are not creating, we're done
613
if(!create)
614       {
615          return null;
616       }
617
618       // create the new preload data map
619
Map JavaDoc preloadDataMap = new HashMap JavaDoc();
620
621       // create new soft reference
622
ref = new SoftReference JavaDoc(preloadDataMap);
623
624       // store the reference
625
manager.putEntityTxData(preloadKey, ref);
626
627       // return the new preload data map
628
return preloadDataMap;
629    }
630
631    private Map JavaDoc getListMap()
632    {
633       return (Map JavaDoc) listMapTxLocal.get();
634    }
635
636    private final class ListCache
637    {
638       private final TransactionLocal cacheTxLocal = new TransactionLocal()
639       {
640          protected Object JavaDoc initialValue()
641          {
642             return new LinkedList JavaDoc();
643          }
644
645          public Transaction JavaDoc getTransaction()
646          {
647             try
648             {
649                return transactionManager.getTransaction();
650             }
651             catch(SystemException JavaDoc e)
652             {
653                throw new IllegalStateException JavaDoc("An error occured while getting the " +
654                   "transaction associated with the current thread: " + e);
655             }
656          }
657       };
658       private int max;
659
660       public ListCache(int max)
661       {
662          if(max < 0)
663             throw new IllegalArgumentException JavaDoc("list-cache-max is negative: " + max);
664          this.max = max;
665       }
666
667       public void add(List JavaDoc list)
668       {
669          if(max == 0)
670          {
671             // we're not caching lists, so we're done
672
return;
673          }
674
675          LinkedList JavaDoc cache = getCache();
676          if(cache == null)
677             return;
678          cache.addFirst(new IdentityObject(list));
679
680          // shrink size to max
681
while(cache.size() > max)
682          {
683             IdentityObject object = (IdentityObject) cache.removeLast();
684             ageOut((List JavaDoc) object.getObject());
685          }
686       }
687
688       public void promote(List JavaDoc list)
689       {
690          if(max == 0)
691          {
692             // we're not caching lists, so we're done
693
return;
694          }
695
696          LinkedList JavaDoc cache = getCache();
697          if(cache == null)
698             return;
699
700          IdentityObject object = new IdentityObject(list);
701          if(cache.remove(object))
702          {
703             // it was in the cache so add it to the front
704
cache.addFirst(object);
705          }
706       }
707
708       public void remove(List JavaDoc list)
709       {
710          if(max == 0)
711          {
712             // we're not caching lists, so we're done
713
return;
714          }
715          LinkedList JavaDoc cache = getCache();
716          if(cache != null)
717             cache.remove(new IdentityObject(list));
718       }
719
720       public void clear()
721       {
722          if(max == 0)
723          {
724             // we're not caching lists, so we're done
725
return;
726          }
727       }
728
729       private void ageOut(List JavaDoc list)
730       {
731          removeFinderResult(list);
732       }
733
734       private LinkedList JavaDoc getCache()
735       {
736          return (LinkedList JavaDoc) cacheTxLocal.get();
737       }
738    }
739
740    /**
741     * Wraps an entity primary key, so it does not collide with other
742     * data stored in the entityTxDataMap.
743     */

744    private static final class PreloadKey
745    {
746       private final Object JavaDoc entityPrimaryKey;
747
748       public PreloadKey(Object JavaDoc entityPrimaryKey)
749       {
750          if(entityPrimaryKey == null)
751          {
752             throw new IllegalArgumentException JavaDoc("Entity primary key is null");
753          }
754          this.entityPrimaryKey = entityPrimaryKey;
755       }
756
757       public boolean equals(Object JavaDoc object)
758       {
759          if(object instanceof PreloadKey)
760          {
761             PreloadKey preloadKey = (PreloadKey) object;
762             return preloadKey.entityPrimaryKey.equals(entityPrimaryKey);
763          }
764          return false;
765       }
766
767       public int hashCode()
768       {
769          return entityPrimaryKey.hashCode();
770       }
771
772       public String JavaDoc toString()
773       {
774          return "PreloadKey: entityId=" + entityPrimaryKey;
775       }
776    }
777
778    private static final class EntityMapEntry
779    {
780       public final int index;
781       public final List JavaDoc results;
782       public final JDBCReadAheadMetaData readahead;
783
784       private EntityMapEntry(
785          int index,
786          List JavaDoc results,
787          JDBCReadAheadMetaData readahead)
788       {
789
790          this.index = index;
791          this.results = results;
792          this.readahead = readahead;
793       }
794    }
795
796    public final static class EntityReadAheadInfo
797    {
798       private final List JavaDoc loadKeys;
799       private final JDBCReadAheadMetaData readahead;
800
801       private EntityReadAheadInfo(List JavaDoc loadKeys)
802       {
803          this(loadKeys, null);
804       }
805
806       private EntityReadAheadInfo(List JavaDoc loadKeys, JDBCReadAheadMetaData r)
807       {
808          this.loadKeys = loadKeys;
809          this.readahead = r;
810       }
811
812       public List JavaDoc getLoadKeys()
813       {
814          return loadKeys;
815       }
816
817       public JDBCReadAheadMetaData getReadAhead()
818       {
819          return readahead;
820       }
821    }
822
823    /**
824     * Wraps an Object and does equals/hashCode based on object identity.
825     */

826    private static final class IdentityObject
827    {
828       private final Object JavaDoc object;
829
830       public IdentityObject(Object JavaDoc object)
831       {
832          if(object == null)
833          {
834             throw new IllegalArgumentException JavaDoc("Object is null");
835          }
836          this.object = object;
837       }
838
839       public Object JavaDoc getObject()
840       {
841          return object;
842       }
843
844       public boolean equals(Object JavaDoc object)
845       {
846          return this.object == object;
847       }
848
849       public int hashCode()
850       {
851          return object.hashCode();
852       }
853
854       public String JavaDoc toString()
855       {
856          return object.toString();
857       }
858    }
859 }
860
Popular Tags