68
|
1 /*
|
|
2 Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com>
|
|
3
|
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5 of this software and associated documentation files (the "Software"), to deal
|
|
6 in the Software without restriction, including without limitation the rights
|
|
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8 copies of the Software, and to permit persons to whom the Software is
|
|
9 furnished to do so, subject to the following conditions:
|
|
10
|
|
11 The above copyright notice and this permission notice shall be included in
|
|
12 all copies or substantial portions of the Software.
|
|
13
|
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
20 THE SOFTWARE.
|
|
21 */
|
|
22
|
|
23 package fschmidt.db.cache;
|
|
24
|
|
25 import java.sql.ResultSet;
|
|
26 import java.sql.SQLException;
|
|
27 import java.util.Map;
|
|
28 import java.util.HashMap;
|
|
29 import java.util.Set;
|
|
30 import java.util.HashSet;
|
|
31 import java.util.Collections;
|
|
32 import java.util.Collection;
|
|
33 import java.util.NoSuchElementException;
|
|
34 import fschmidt.db.util.Unique;
|
|
35 import fschmidt.db.util.WeakCacheMap;
|
|
36 import fschmidt.db.DbObject;
|
|
37 import fschmidt.db.DbKey;
|
|
38 import fschmidt.db.DbRecord;
|
|
39 import fschmidt.db.extend.DbRecordExt;
|
|
40 import fschmidt.db.extend.DbTableExt;
|
|
41 import fschmidt.db.extend.DbDatabaseExt;
|
|
42 import fschmidt.db.extend.DbTransaction;
|
|
43 import fschmidt.db.extend.FilterTable;
|
|
44
|
|
45
|
|
46 final class DbTableImpl<K extends DbKey,V extends DbObject<K,V>> extends FilterTable<K,V> {
|
|
47 private static final DbObject NULL0 = new DbObject(){
|
|
48 public DbRecord getDbRecord() { throw new RuntimeException(); }
|
|
49 };
|
|
50
|
|
51 @SuppressWarnings("unchecked")
|
|
52 private final V NULL = (V)NULL0; // hack
|
|
53
|
|
54 private static class CacheKey {
|
|
55 private final DbTableImpl table;
|
|
56 final Object key;
|
|
57
|
|
58 CacheKey(DbTableImpl table,Object key) {
|
|
59 this.table = table;
|
|
60 this.key = key;
|
|
61 }
|
|
62
|
|
63 @Override public boolean equals(Object obj) {
|
|
64 if( obj == this )
|
|
65 return true;
|
|
66 if( !(obj instanceof DbTableImpl.CacheKey) )
|
|
67 return false;
|
|
68 CacheKey ck = (CacheKey)obj;
|
|
69 return ck.table == table && ck.key.equals(key);
|
|
70 }
|
|
71
|
|
72 @Override public int hashCode() {
|
|
73 return table.hashCode() + key.hashCode();
|
|
74 }
|
|
75
|
|
76 }
|
|
77
|
|
78 private static final Map<CacheKey,DbObject> cache0 = Collections.synchronizedMap(new WeakCacheMap<CacheKey,DbObject>());
|
|
79 private final Unique<K> unique = new Unique<K>();
|
|
80 private final DbDatabaseImpl database;
|
|
81
|
|
82 DbTableImpl(DbTableExt<K,V> table,DbDatabaseImpl database) {
|
|
83 super(database,table);
|
|
84 this.database = database;
|
|
85 }
|
|
86
|
|
87 private Map<CacheKey,DbObject> transCache() {
|
|
88 Map<String,Object> transMap = database.transactionMap();
|
|
89 @SuppressWarnings("unchecked")
|
|
90 Map<CacheKey,DbObject> transCache = (Map<CacheKey,DbObject>)transMap.get("transCache");
|
|
91 if( transCache == null ) {
|
|
92 transCache = new WeakCacheMap<CacheKey,DbObject>();
|
|
93 transMap.put( "transCache", transCache );
|
|
94 }
|
|
95 return transCache;
|
|
96 }
|
|
97
|
|
98 private final boolean isStale(DbObject obj) {
|
|
99 if( obj==null )
|
|
100 return true;
|
|
101 if( obj==NULL )
|
|
102 return false;
|
|
103 /*
|
|
104 if( database.isInTransaction() ) {
|
|
105 return transCache().containsKey(obj.getDbRecord().getPrimaryKey());
|
|
106 } else {
|
|
107 return obj.getDbRecord().isStale();
|
|
108 }
|
|
109 */
|
|
110 return obj.getDbRecord().isStale();
|
|
111 }
|
|
112
|
|
113 public final V findByPrimaryKey(K key) {
|
|
114 CacheKey ck = new CacheKey(this,key);
|
|
115 if( database.isInTransaction() ) {
|
|
116 Map<CacheKey,DbObject> cache = transCache();
|
|
117 @SuppressWarnings("unchecked")
|
|
118 V obj = (V)cache.get(ck);
|
|
119 if( obj==null ) {
|
|
120 obj = table.findByPrimaryKey(key);
|
|
121 if( obj==null )
|
|
122 obj = NULL;
|
|
123 cache.put(ck,obj);
|
|
124 }
|
|
125 return obj==NULL ? null : obj;
|
|
126 }
|
|
127 Map<CacheKey,DbObject> cache = cache0;
|
|
128 key = unique.get(key);
|
|
129 try {
|
|
130 synchronized(key) {
|
|
131 @SuppressWarnings("unchecked")
|
|
132 V obj = (V)cache.get(ck);
|
|
133 if( !isStale(obj) )
|
|
134 return obj==NULL ? null : obj;
|
|
135 obj = table.findByPrimaryKey(key);
|
|
136 if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) )
|
|
137 throw new RuntimeException();
|
|
138 return obj;
|
|
139 }
|
|
140 } finally {
|
|
141 unique.free(key);
|
|
142 }
|
|
143 }
|
|
144
|
|
145 public final Map<K,V> findByPrimaryKey(Collection<K> keys) {
|
|
146 Map<K,V> map = new HashMap<K,V>();
|
|
147 Set<K> set = new HashSet<K>();
|
|
148 Map<CacheKey,DbObject> cache = database.isInTransaction() ? transCache() : cache0;
|
|
149 boolean inTrans = database.isInTransaction();
|
|
150 for( K key : keys ) {
|
|
151 CacheKey ck = new CacheKey(this,key);
|
|
152 @SuppressWarnings("unchecked")
|
|
153 V obj = (V)cache.get(ck);
|
|
154 if( obj==null || !inTrans && isStale(obj) ) {
|
|
155 set.add(key);
|
|
156 } else {
|
|
157 map.put( key, obj==NULL ? null : obj );
|
|
158 }
|
|
159 }
|
|
160 if( !set.isEmpty() ) {
|
|
161 Map<K,V> objs = table.findByPrimaryKey(set);
|
|
162 for( K key : set ) {
|
|
163 CacheKey ck = new CacheKey(this,key);
|
|
164 if( inTrans ) {
|
|
165 if( cache.get(ck) != null )
|
|
166 throw new RuntimeException();
|
|
167 V obj = table.findByPrimaryKey(key);
|
|
168 if( obj==null )
|
|
169 obj = NULL;
|
|
170 cache.put(ck,obj);
|
|
171 map.put( key, obj==NULL ? null : obj );
|
|
172 } else {
|
|
173 key = unique.get(key);
|
|
174 try {
|
|
175 synchronized(key) {
|
|
176 @SuppressWarnings("unchecked")
|
|
177 V obj = (V)cache.get(ck);
|
|
178 if( isStale(obj) ) {
|
|
179 obj = objs.get(key);
|
|
180 if( obj == null )
|
|
181 obj = NULL;
|
|
182 if( !isStale( cache.put(ck,obj) ) )
|
|
183 throw new RuntimeException();
|
|
184 }
|
|
185 map.put( key, obj==NULL ? null : obj );
|
|
186 }
|
|
187 } finally {
|
|
188 unique.free(key);
|
|
189 }
|
|
190 }
|
|
191 }
|
|
192 }
|
|
193 return map;
|
|
194 }
|
|
195
|
|
196 public V getDbObject(K key,ResultSet rs,String tableName)
|
|
197 throws SQLException
|
|
198 {
|
|
199 CacheKey ck = new CacheKey(this,key);
|
|
200 if( database.isInTransaction() ) {
|
|
201 Map<CacheKey,DbObject> cache = transCache();
|
|
202 @SuppressWarnings("unchecked")
|
|
203 V obj = (V)cache.get(ck);
|
|
204 if( obj==null ) {
|
|
205 obj = table.getDbObject(key,rs,tableName);
|
|
206 if( obj==null )
|
|
207 obj = NULL;
|
|
208 cache.put(ck,obj);
|
|
209 }
|
|
210 return obj==NULL ? null : obj;
|
|
211 }
|
|
212 Map<CacheKey,DbObject> cache = cache0;
|
|
213 key = unique.get(key);
|
|
214 try {
|
|
215 synchronized(key) {
|
|
216 @SuppressWarnings("unchecked")
|
|
217 V obj = (V)cache.get(ck);
|
|
218 if( !isStale(obj) )
|
|
219 return obj==NULL ? null : obj;
|
|
220 obj = table.getDbObject(key,rs,tableName);
|
|
221 if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) )
|
|
222 throw new RuntimeException();
|
|
223 return obj;
|
|
224 }
|
|
225 } finally {
|
|
226 unique.free(key);
|
|
227 }
|
|
228 }
|
|
229
|
|
230 public void uncache(final K key) {
|
|
231 if( key == null )
|
|
232 return;
|
|
233 final CacheKey ck = new CacheKey(this,key);
|
|
234 if( database.isInTransaction() ) {
|
|
235 doUncache(ck,transCache());
|
|
236 }
|
|
237 database.runJustAfterCommit( new Runnable() {
|
|
238 public void run() {
|
|
239 doUncache(ck,cache0);
|
|
240 }
|
|
241 } );
|
|
242 }
|
|
243
|
|
244 private static void doUncache(CacheKey ck,Map<CacheKey,DbObject> cache) {
|
|
245 DbObject obj = cache.remove(ck);
|
|
246 if( obj!=null && obj!=NULL0 ) {
|
|
247 DbRecordExt rec = (DbRecordExt)obj.getDbRecord();
|
|
248 rec.makeStale();
|
|
249 }
|
|
250 }
|
|
251
|
|
252 static void clearCache() {
|
|
253 while( !cache0.isEmpty() ) {
|
|
254 CacheKey[] a = cache0.keySet().toArray(new CacheKey[0]);
|
|
255 for( CacheKey ck : a ) {
|
|
256 doUncache( ck, cache0 );
|
|
257 }
|
|
258 }
|
|
259 }
|
|
260
|
|
261 }
|