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