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