1 /** 2 * Copyright 2005 Steve Molloy 3 * 4 * This file is part of OV4J. 5 * 6 * OV4J is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as 7 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 8 * 9 * OV4J is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 10 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 11 * 12 * You should have received a copy of the GNU General Public License along with OV4J; if not, write to the Free Software 13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 14 * 15 */ 16 package org.ov4j.cache; 17 18 import java.io.IOException; 19 import java.lang.reflect.Method; 20 import java.util.Arrays; 21 import java.util.TreeMap; 22 import java.util.Vector; 23 import java.util.logging.Level; 24 import java.util.logging.Logger; 25 26 import org.ov4j.Config; 27 import org.ov4j.Controler; 28 import org.ov4j.IContainer; 29 import org.ov4j.comp.ComparisonResult; 30 import org.ov4j.data.ClassComparable; 31 import org.ov4j.data.Version; 32 33 /** 34 * This class will handle caching results of calls. 35 * 36 * @author smolloy 37 * 38 */ 39 public class CacheHandler { 40 /** 41 * Logger for this class 42 */ 43 private static final Logger logger = 44 Logger 45 .getLogger(CacheHandler.class 46 .getName()); 47 48 /** Author used when caching. */ 49 protected static final String CACHE_AUTHOR = 50 Config 51 .getString("OV4J.cache.Author"); 52 53 /** Comment on normal cache. */ 54 protected static final String NORMAL_COMMENT = 55 Config 56 .getString("OV4J.cache.NormalComment"); 57 58 /** Comment for timeouts */ 59 protected static final String TIMEOUT_COMMENT = 60 Config 61 .getString("OV4J.cache.TimeoutComment"); 62 63 /** Comment for error. */ 64 protected static final String ERROR_COMMENT = 65 Config 66 .getString("OV4J.cache.ErrorComment"); 67 68 /** Number of versions to keep in history. */ 69 protected static final int CACHE_VERSION_ROLLOVER = 70 Integer 71 .parseInt(Config 72 .getString("OV4J.CacheVersionRollover")); 73 74 /** Controler used for caching. */ 75 private final Controler<ClassComparable, CachedResultId> theCtrl; 76 77 /** Timeout before using cached result. */ 78 private long timeout = 30000; 79 80 /** Timeout after which the call is considered to fail, even for caching. */ 81 private long delayedCacheTimeout = 60000; 82 83 /** Current results. */ 84 private final TreeMap<CachedResultId, Version<ClassComparable>> results = 85 new TreeMap<CachedResultId, Version<ClassComparable>>(); 86 87 /** Listeners who want to receive notifications. */ 88 private final Vector<ICacheHandlerListener> listeners = 89 new Vector<ICacheHandlerListener>(); 90 91 /** Whether or not we are running offline. */ 92 private boolean offline = false; 93 94 /** 95 * Constructor. 96 * 97 * @param cont 98 * Container to used for storing data. 99 */ 100 public CacheHandler(final IContainer<ClassComparable, CachedResultId> cont) { 101 theCtrl = new Controler<ClassComparable, CachedResultId>(cont); 102 setVersionRollover(CacheHandler.CACHE_VERSION_ROLLOVER); 103 } 104 105 /** 106 * Register listener for notifications. 107 * 108 * @param listener 109 * Listener to be added. 110 * @return Whether or not the listener was added. 111 */ 112 public boolean addListener(final ICacheHandlerListener listener) { 113 if (CacheHandler.logger.isLoggable(Level.FINEST)) { 114 CacheHandler.logger 115 .entering("CacheHandler", "addListener(ICacheHandlerListener=" + listener + ")", "start"); 116 } 117 118 final boolean returnboolean = listeners.add(listener); 119 120 if (CacheHandler.logger.isLoggable(Level.FINEST)) { 121 CacheHandler.logger.exiting("CacheHandler", "addListener(ICacheHandlerListener=" + listener + ")", 122 "end - return value=" + returnboolean); 123 } 124 return returnboolean; 125 } 126 127 /** 128 * Add result to cache. 129 * 130 * @param id 131 * ID to use for storing result. 132 * @param result 133 * Result to be stored. 134 * @throws IOException 135 */ 136 protected void addNCommit(final CachedResultId id, final Version<ClassComparable> result) throws IOException { 137 if (CacheHandler.logger.isLoggable(Level.FINER)) { 138 CacheHandler.logger.entering("CacheHandler", "addNCommit(CachedResultId=" + id 139 + ", Version<ClassComparable>=" + result + ")", "start"); 140 } 141 142 if (!theCtrl.isClosed()) { 143 if (theCtrl.addNCommit(id, result)) { 144 theCtrl.saveChanges(); 145 } else { 146 theCtrl.discardChanges(); 147 } 148 } 149 150 if (CacheHandler.logger.isLoggable(Level.FINER)) { 151 CacheHandler.logger.exiting("CacheHandler", "addNCommit(CachedResultId=" + id 152 + ", Version<ClassComparable>=" + result + ")", "end"); 153 } 154 } 155 156 /** 157 * Perform a cached call, will wait for a period of time before returning the result from the cache if it exists. 158 * After that period, a background thread will wait another period of time to see if the call makes it and if the 159 * cache can be updated. 160 * 161 * @param theMethod 162 * Method to be called. 163 * @param instance 164 * Object on which the method should be called. 165 * @param args 166 * Arguments to pass to the method call. 167 * @return The result of the call. 168 */ 169 public Version<ClassComparable> cachedCall(final Method theMethod, final Object instance, final Object... args) { 170 if (CacheHandler.logger.isLoggable(Level.FINER)) { 171 CacheHandler.logger.entering("CacheHandler", "cachedCall(Method=" + theMethod + ", Object=" + instance 172 + ", Object=" + Arrays.toString(args) + ")", "start"); 173 } 174 175 boolean newVersion = false; 176 final MethodCaller theCaller = new MethodCaller(theMethod, instance, args); 177 Version<ClassComparable> result = null; 178 final CachedResultId id = new CachedResultId(); 179 id.setMethod(theMethod.toString()); 180 if (args != null && args.length == 1 && args[0] instanceof Comparable) { 181 id.setArgs(args); 182 } else { 183 final ClassComparable cc = new ClassComparable(); 184 cc.setTheObject(args); 185 id.setArgs(cc); 186 } 187 prefetchLatest(id); 188 theCaller.call(timeout); 189 if (theCaller.hasTimedout()) { 190 result = latest(id); 191 if (result != null) { 192 result.setComment(CacheHandler.TIMEOUT_COMMENT); 193 } 194 setOffline(true); 195 } else if (theCaller.hasFailed()) { 196 result = latest(id); 197 if (result != null) { 198 result.setComment(CacheHandler.ERROR_COMMENT + theCaller.getError().toString()); 199 } 200 setOffline(true); 201 } else { 202 final Object res = theCaller.getTheResult(); 203 if (res != null) { 204 ClassComparable theObject = null; 205 if (res instanceof ClassComparable) { 206 theObject = (ClassComparable) res; 207 } else { 208 theObject = new ClassComparable(); 209 theObject.setTheObject(res); 210 } 211 if (theObject != null) { 212 result = latest(id); 213 if (result == null) { 214 result = new Version<ClassComparable>(); 215 result.setAuthor(CacheHandler.CACHE_AUTHOR); 216 result.setVersionNumber(0); 217 newVersion = true; 218 } 219 result.setComment(CacheHandler.NORMAL_COMMENT); 220 result.setVersionedObject(theObject); 221 setOffline(false); 222 } 223 } 224 } 225 final DelayedCacher dc = new DelayedCacher(theCaller, result, delayedCacheTimeout, id, this, newVersion); 226 final Thread t = new Thread(dc); 227 t.setPriority(Thread.MIN_PRIORITY); 228 t.start(); 229 230 if (CacheHandler.logger.isLoggable(Level.FINER)) { 231 CacheHandler.logger.exiting("CacheHandler", "cachedCall(Method=" + theMethod + ", Object=" + instance 232 + ", Object=" + Arrays.toString(args) + ")", "end - return value=" + result); 233 } 234 return result; 235 } 236 237 /** 238 * Clear the cache. 239 * 240 */ 241 public void clear() { 242 if (CacheHandler.logger.isLoggable(Level.FINER)) { 243 CacheHandler.logger.entering("CacheHandler", "clear()", "start"); 244 } 245 246 theCtrl.clear(); 247 248 if (CacheHandler.logger.isLoggable(Level.FINER)) { 249 CacheHandler.logger.exiting("CacheHandler", "clear()", "end"); 250 } 251 } 252 253 /** 254 * Close the cache. 255 * 256 */ 257 public void close() { 258 if (CacheHandler.logger.isLoggable(Level.FINER)) { 259 CacheHandler.logger.entering("CacheHandler", "close()", "start"); 260 } 261 262 theCtrl.close(); 263 264 if (CacheHandler.logger.isLoggable(Level.FINER)) { 265 CacheHandler.logger.exiting("CacheHandler", "close()", "end"); 266 } 267 } 268 269 /** 270 * Commit the result in cache. 271 * 272 * @param id 273 * ID to used for storing result. 274 * @param result 275 * Result to be stored. 276 * @throws InstantiationException 277 * @throws IllegalAccessException 278 * @throws ClassNotFoundException 279 * @throws IOException 280 */ 281 protected void commit(final CachedResultId id, final Version<ClassComparable> result) 282 throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException { 283 if (CacheHandler.logger.isLoggable(Level.FINER)) { 284 CacheHandler.logger.entering("CacheHandler", "commit(CachedResultId=" + id + ", Version<ClassComparable>=" 285 + result + ")", "start"); 286 } 287 288 if (!theCtrl.isClosed()) { 289 if (theCtrl.commit(id, result, (Class<ComparisonResult<ClassComparable>>) Class 290 .forName("org.ov4j.comp.DummyComparisonResult"))) { 291 theCtrl.saveChanges(); 292 } else { 293 theCtrl.discardChanges(); 294 } 295 } 296 297 if (CacheHandler.logger.isLoggable(Level.FINER)) { 298 CacheHandler.logger.exiting("CacheHandler", "commit(CachedResultId=" + id + ", Version<ClassComparable>=" 299 + result + ")", "end"); 300 } 301 } 302 303 /** 304 * @return Returns the delayedCacheTimeout. 305 */ 306 public long getDelayedCacheTimeout() { 307 return delayedCacheTimeout; 308 } 309 310 /** 311 * @return Returns the timeout. 312 */ 313 public long getTimeout() { 314 return timeout; 315 } 316 317 /** 318 * 319 * @return The version rollover. 320 */ 321 public int getVersionRollover() { 322 return theCtrl.getVersionRollover(); 323 } 324 325 /** 326 * Fetch latest result for the given ID. 327 * 328 * @param id 329 * ID to be fetched. 330 * @return The latest result for the given ID. 331 */ 332 protected Version<ClassComparable> latest(final CachedResultId id) { 333 if (CacheHandler.logger.isLoggable(Level.FINER)) { 334 CacheHandler.logger.entering("CacheHandler", "latest(CachedResultId=" + id + ")", "start"); 335 } 336 337 while (!results.containsKey(id)) { 338 Thread.yield(); 339 } 340 final Version<ClassComparable> returnVersion = results.get(id); 341 342 if (CacheHandler.logger.isLoggable(Level.FINER)) { 343 CacheHandler.logger.exiting("CacheHandler", "latest(CachedResultId=" + id + ")", "end - return value=" 344 + returnVersion); 345 } 346 return returnVersion; 347 } 348 349 /** 350 * Starts a thread which will try fetching latest result in the background in case it is needed. 351 * 352 * @param id 353 * ID to prefetch. 354 */ 355 private void prefetchLatest(final CachedResultId id) { 356 if (CacheHandler.logger.isLoggable(Level.FINER)) { 357 CacheHandler.logger.entering("CacheHandler", "prefetchLatest(CachedResultId=" + id + ")", "start"); 358 } 359 360 final ResultFetcher t = new ResultFetcher(results, theCtrl, id); 361 t.start(); 362 363 if (CacheHandler.logger.isLoggable(Level.FINER)) { 364 CacheHandler.logger.exiting("CacheHandler", "prefetchLatest(CachedResultId=" + id + ")", "end"); 365 } 366 } 367 368 /** 369 * Unregister listener for notifications. 370 * 371 * @param listener 372 * Listener to be removed. 373 * @return Whether or not the listener was found and removed. 374 */ 375 public boolean removeListener(final ICacheHandlerListener listener) { 376 if (CacheHandler.logger.isLoggable(Level.FINEST)) { 377 CacheHandler.logger.entering("CacheHandler", "removeListener(ICacheHandlerListener=" + listener + ")", 378 "start"); 379 } 380 381 final boolean returnboolean = listeners.remove(listener); 382 383 if (CacheHandler.logger.isLoggable(Level.FINEST)) { 384 CacheHandler.logger.exiting("CacheHandler", "removeListener(ICacheHandlerListener=" + listener + ")", 385 "end - return value=" + returnboolean); 386 } 387 return returnboolean; 388 } 389 390 /** 391 * @param delayedCacheTimeout 392 * The delayedCacheTimeout to set. 393 */ 394 public void setDelayedCacheTimeout(final long delayedCacheTimeout) { 395 this.delayedCacheTimeout = delayedCacheTimeout; 396 } 397 398 /** 399 * Set offline, will notify listeners. 400 * 401 * @param off 402 * Whether or not we are running offline (normally when last call timedout). 403 */ 404 private void setOffline(final boolean off) { 405 if (CacheHandler.logger.isLoggable(Level.FINER)) { 406 CacheHandler.logger.entering("CacheHandler", "setOffline(boolean=" + off + ")", "start"); 407 } 408 409 if (offline != off) { 410 offline = off; 411 for (int i = 0; i < listeners.size(); i++) { 412 listeners.elementAt(i).setOffline(offline); 413 } 414 } 415 416 if (CacheHandler.logger.isLoggable(Level.FINER)) { 417 CacheHandler.logger.exiting("CacheHandler", "setOffline(boolean=" + off + ")", "end"); 418 } 419 } 420 421 /** 422 * @param timeout 423 * The timeout to set. 424 */ 425 public void setTimeout(final long timeout) { 426 this.timeout = timeout; 427 } 428 429 /** 430 * 431 * @param versionRollover 432 * The version rollover to set. 433 */ 434 public void setVersionRollover(final int versionRollover) { 435 theCtrl.setVersionRollover(versionRollover); 436 } 437 }