View Javadoc

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 }