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.retry;
17  
18  import java.io.IOException;
19  import java.lang.reflect.Method;
20  import java.util.Arrays;
21  import java.util.Hashtable;
22  import java.util.Random;
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.data.ClassComparable;
30  import org.ov4j.data.Item;
31  import org.ov4j.data.Version;
32  
33  /**
34   * This is the main handler of all retried calls.
35   * 
36   * @author smolloy
37   * 
38   */
39  public class RetryHandler {
40  	/**
41  	 * Logger for this class
42  	 */
43  	private static final Logger									logger					=
44  																							Logger
45  																								.getLogger(RetryHandler.class
46  																									.getName());
47  
48  	/** Author used for retries. */
49  	private static final String									RETRY_AUTHOR			=
50  																							Config
51  																								.getString("OV4J.retry.Author");
52  
53  	/** Comment used for retries. */
54  	private static final String									RETRY_COMMENT			=
55  																							Config
56  																								.getString("OV4J.retry.Comment");
57  
58  	/** Comment for cached versions */
59  	private static final String									RETRY_CACHED_COMMENT	=
60  																							Config
61  																								.getString("OV4J.retry.CachedVersionComment");
62  
63  	/** Number of versions to keep in history. */
64  	private static final int									RETRY_VERSION_ROLLOVER	=
65  																							Integer
66  																								.parseInt(Config
67  																									.getString("OV4J.RetryVersionRollover"));
68  
69  	/** Randomizer for cached args IDs. */
70  	private static volatile Random								theRandom				= new Random();
71  
72  	/** Controler used to store retry data. */
73  	private final Controler<ClassComparable, RetryableCallId>	theCtrl;
74  
75  	/** Controler used to store arguments. */
76  	private final Controler<ClassComparable, Long>				theArgCtrl;
77  
78  	/** Hashtable containing live instances for given classes. */
79  	private final Hashtable<String, IRetryable>					liveInstances			=
80  																							new Hashtable<String, IRetryable>();
81  
82  	/** Interval between retries. */
83  	private long												retryInterval			=
84  																							Long
85  																								.parseLong(Config
86  																									.getString("OV4J.DefaultRetryInterval"));
87  
88  	/** Maximum number of retries. */
89  	private int													maximumRetries			=
90  																							Integer
91  																								.parseInt(Config
92  																									.getString("OV4J.maximumRetries"));
93  
94  	/**
95  	 * Constructor.
96  	 * 
97  	 * @param cont
98  	 *            The container in which to store data.
99  	 * @param argCont
100 	 *            The container in which to store arguments
101 	 * @throws IOException
102 	 */
103 	public RetryHandler(final IContainer<ClassComparable, RetryableCallId> cont,
104 		final IContainer<ClassComparable, Long> argCont) throws IOException {
105 		theCtrl = new Controler<ClassComparable, RetryableCallId>(cont);
106 		theArgCtrl = new Controler<ClassComparable, Long>(argCont);
107 		setVersionRollover(RetryHandler.RETRY_VERSION_ROLLOVER);
108 		retryFromCache();
109 	}
110 
111 	/**
112 	 * Cache arguments of the call.
113 	 * 
114 	 * @param args
115 	 *            Arguments to be cached.
116 	 * @return Unique ID to retrieve the arguments.
117 	 */
118 	public Long cacheArgs(final Object... args) {
119 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
120 			RetryHandler.logger.entering("RetryHandler", "cacheArgs(Object=" + Arrays.toString(args) + ")", "start");
121 		}
122 
123 		Long id = null;
124 		try {
125 			id = RetryHandler.theRandom.nextLong();
126 			final Version<ClassComparable> theCachedVersion = new Version<ClassComparable>();
127 			theCachedVersion.setAuthor(RetryHandler.RETRY_AUTHOR);
128 			theCachedVersion.setComment(RetryHandler.RETRY_CACHED_COMMENT);
129 			final ClassComparable cc = new ClassComparable();
130 			cc.setTheObject(args);
131 			theCachedVersion.setVersionedObject(cc);
132 			if (theArgCtrl.addNCommit(id, theCachedVersion)) {
133 				theArgCtrl.saveChanges();
134 			} else {
135 				theArgCtrl.discardChanges();
136 				id = null;
137 			}
138 		} catch (final Exception e) {
139 			if (RetryHandler.logger.isLoggable(Level.FINE)) {
140 				RetryHandler.logger.logp(Level.FINE, "RetryHandler", "cacheArgs(Object=" + Arrays.toString(args) + ")",
141 					"Exception caught", e);
142 			}
143 
144 			id = null;
145 		}
146 
147 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
148 			RetryHandler.logger.exiting("RetryHandler", "cacheArgs(Object=" + Arrays.toString(args) + ")",
149 				"end - return value=" + id);
150 		}
151 		return id;
152 	}
153 
154 	/**
155 	 * The call is done, remove it from cache.
156 	 * 
157 	 * @param call
158 	 *            Call which is done.
159 	 * @throws IOException
160 	 */
161 	protected void callDone(final RetryableCall call) throws IOException {
162 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
163 			RetryHandler.logger.entering("RetryHandler", "callDone(RetryableCall=" + call + ")", "start");
164 		}
165 
166 		final Long argId = call.getArgs();
167 		final RetryableCallId theId = new RetryableCallId();
168 		theId.setArgs(call.getArgs());
169 		theId.setClassName(call.getClassName());
170 		theId.setMethodName(call.getMethod().toString());
171 		if (theCtrl.delete(theId)) {
172 			if (theArgCtrl.delete(argId)) {
173 				theCtrl.saveChanges();
174 				theArgCtrl.saveChanges();
175 			} else {
176 				theCtrl.discardChanges();
177 				theArgCtrl.discardChanges();
178 			}
179 		}
180 
181 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
182 			RetryHandler.logger.exiting("RetryHandler", "callDone(RetryableCall=" + call + ")", "end");
183 		}
184 	}
185 
186 	/**
187 	 * Clear all retry data.
188 	 */
189 	public void clear() {
190 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
191 			RetryHandler.logger.entering("RetryHandler", "clear()", "start");
192 		}
193 
194 		theCtrl.clear();
195 		theArgCtrl.clear();
196 
197 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
198 			RetryHandler.logger.exiting("RetryHandler", "clear()", "end");
199 		}
200 	}
201 
202 	/**
203 	 * Close the handler.
204 	 */
205 	public void close() {
206 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
207 			RetryHandler.logger.entering("RetryHandler", "close()", "start");
208 		}
209 
210 		theCtrl.close();
211 		theArgCtrl.close();
212 
213 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
214 			RetryHandler.logger.exiting("RetryHandler", "close()", "end");
215 		}
216 	}
217 
218 	/**
219 	 * Find an instance of the given class.
220 	 * 
221 	 * @param className
222 	 * @return An instance of the specified classname, or null if none was found.
223 	 */
224 	protected IRetryable findInstance(final String className) {
225 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
226 			RetryHandler.logger.entering("RetryHandler", "findInstance(String=" + className + ")", "start");
227 		}
228 
229 		final IRetryable returnIRetryable = liveInstances.get(className);
230 
231 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
232 			RetryHandler.logger.exiting("RetryHandler", "findInstance(String=" + className + ")", "end - return value="
233 				+ returnIRetryable);
234 		}
235 		return returnIRetryable;
236 	}
237 
238 	/**
239 	 * Retrieve the arguments from cache.
240 	 * 
241 	 * @param id
242 	 *            Unique id to fetch.
243 	 * @return The arguments associated with this id.
244 	 * @throws IOException
245 	 */
246 	public Object[] getArgs(final Long id) throws IOException {
247 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
248 			RetryHandler.logger.entering("RetryHandler", "getArgs(Long=" + id + ")", "start");
249 		}
250 
251 		final Version<ClassComparable> theVersion = theArgCtrl.latest(id);
252 		final Object[] returnObjectArray = (Object[]) theVersion.getVersionedObject().getTheObject();
253 
254 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
255 			RetryHandler.logger.exiting("RetryHandler", "getArgs(Long=" + id + ")", "end - return value="
256 				+ Arrays.toString(returnObjectArray));
257 		}
258 		return returnObjectArray;
259 	}
260 
261 	/**
262 	 * @return Returns the maximumRetries.
263 	 */
264 	public int getMaximumRetries() {
265 		return maximumRetries;
266 	}
267 
268 	/**
269 	 * @return Returns the retryInterval.
270 	 */
271 	public long getRetryInterval() {
272 		return retryInterval;
273 	}
274 
275 	/**
276 	 * 
277 	 * @return The version rollover.
278 	 */
279 	public int getVersionRollover() {
280 		return theCtrl.getVersionRollover();
281 	}
282 
283 	/**
284 	 * Registers the instance to receive retry callbacks.
285 	 * 
286 	 * NOTES: - Registering a second instance of the same class will override the first one. - Make sure to unregister
287 	 * the instances when they are no longer needed or they will not get garbage collected as the RetryHandler will keep
288 	 * a reference.
289 	 * 
290 	 * @param cls
291 	 *            Class handled by this instance
292 	 * @param instance
293 	 */
294 	public void register(final Class<?> cls, final IRetryable instance) {
295 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
296 			RetryHandler.logger.entering("RetryHandler", "register(Class<?>=" + cls + ", IRetryable=" + instance + ")",
297 				"start");
298 		}
299 
300 		liveInstances.put(cls.getName(), instance);
301 
302 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
303 			RetryHandler.logger.exiting("RetryHandler", "register(Class<?>=" + cls + ", IRetryable=" + instance + ")",
304 				"end");
305 		}
306 	}
307 
308 	/**
309 	 * Retry a call.
310 	 * 
311 	 * @param theMethod
312 	 *            Method to be called.
313 	 * @param instance
314 	 *            Object on which to call the method.
315 	 * @param args
316 	 *            Arguments to pass to the call.
317 	 * @return Whether or not the call was successfully registered for retry.
318 	 */
319 	public boolean retry(final Method theMethod, final Object instance, final Object... args) {
320 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
321 			RetryHandler.logger.entering("RetryHandler", "retry(Method=" + theMethod + ", Object=" + instance
322 				+ ", Object=" + Arrays.toString(args) + ")", "start");
323 		}
324 
325 		boolean res = false;
326 		try {
327 			final RetryableCallId theId = new RetryableCallId();
328 			theId.setArgs(cacheArgs(args));
329 			theId.setClassName(instance.getClass().getName());
330 			theId.setMethodName(theMethod.toString());
331 			final RetryableCall call = new RetryableCall();
332 			call.setArgs(theId.getArgs());
333 			call.setClassName(theId.getClassName());
334 			call.setMethod(theMethod);
335 			call.setNumRetries(0);
336 			final Version<ClassComparable> v = new Version<ClassComparable>();
337 			v.setAuthor(RetryHandler.RETRY_AUTHOR);
338 			v.setComment(RetryHandler.RETRY_COMMENT);
339 			v.setVersionNumber(0);
340 			final ClassComparable cc = new ClassComparable();
341 			cc.setTheObject(call);
342 			v.setVersionedObject(cc);
343 			res = theCtrl.addNCommit(theId, v);
344 			if (res) {
345 				theCtrl.saveChanges();
346 				final RetryCaller theCaller = new RetryCaller(call, this);
347 				final Thread t = new Thread(theCaller);
348 				t.setPriority(Thread.MIN_PRIORITY);
349 				Thread.sleep(50);
350 				t.start();
351 			} else {
352 				theCtrl.discardChanges();
353 			}
354 		} catch (final IOException e) {
355 			if (RetryHandler.logger.isLoggable(Level.FINE)) {
356 				RetryHandler.logger.logp(Level.FINE, "RetryHandler", "retry(Method=" + theMethod + ", Object="
357 					+ instance + ", Object=" + Arrays.toString(args) + ")", "exception ignored", e);
358 			}
359 		} catch (final InterruptedException e) {
360 			if (RetryHandler.logger.isLoggable(Level.FINE)) {
361 				RetryHandler.logger.logp(Level.FINE, "RetryHandler", "retry(Method=" + theMethod + ", Object="
362 					+ instance + ", Object=" + Arrays.toString(args) + ")", "exception ignored", e);
363 			}
364 		}
365 
366 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
367 			RetryHandler.logger.exiting("RetryHandler", "retry(Method=" + theMethod + ", Object=" + instance
368 				+ ", Object=" + Arrays.toString(args) + ")", "end - return value=" + res);
369 		}
370 		return res;
371 	}
372 
373 	/**
374 	 * Loads the retryable calls previously cached so they can be retried after a VM shutdown.
375 	 * 
376 	 * @throws IOException
377 	 * 
378 	 */
379 	private void retryFromCache() throws IOException {
380 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
381 			RetryHandler.logger.entering("RetryHandler", "retryFromCache()", "start");
382 		}
383 
384 		final RetryableCallId dummy = new RetryableCallId();
385 		final Item<ClassComparable, RetryableCallId> it = new Item<ClassComparable, RetryableCallId>();
386 		it.setId(dummy);
387 		final Version<ClassComparable>[] calls = theCtrl.modifiedSince(-1);
388 		for (int i = 0; i < calls.length; i++) {
389 			if (calls[i].getVersionedObject().getTheObject() instanceof RetryableCall) {
390 				final RetryableCall rc = (RetryableCall) calls[i].getVersionedObject().getTheObject();
391 				final RetryCaller theCaller = new RetryCaller(rc, this);
392 				final Thread t = new Thread(theCaller);
393 				t.setPriority(Thread.MIN_PRIORITY);
394 				t.start();
395 			}
396 		}
397 
398 		if (RetryHandler.logger.isLoggable(Level.FINER)) {
399 			RetryHandler.logger.exiting("RetryHandler", "retryFromCache()", "end");
400 		}
401 	}
402 
403 	/**
404 	 * @param maximumRetries
405 	 *            The maximumRetries to set.
406 	 */
407 	public void setMaximumRetries(final int maximumRetries) {
408 		this.maximumRetries = maximumRetries;
409 	}
410 
411 	/**
412 	 * @param retryInterval
413 	 *            The retryInterval to set.
414 	 */
415 	public void setRetryInterval(final long retryInterval) {
416 		this.retryInterval = retryInterval;
417 	}
418 
419 	/**
420 	 * 
421 	 * @param versionRollover
422 	 *            The version rollover to set.
423 	 */
424 	public void setVersionRollover(final int versionRollover) {
425 		theCtrl.setVersionRollover(versionRollover);
426 		theArgCtrl.setVersionRollover(versionRollover);
427 	}
428 
429 	/**
430 	 * Unregisters the instance.
431 	 * 
432 	 * @see #register(IRetryable instance)
433 	 * 
434 	 * @param cls
435 	 */
436 	public void unregister(final Class<?> cls) {
437 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
438 			RetryHandler.logger.entering("RetryHandler", "unregister(Class<?>=" + cls + ")", "start");
439 		}
440 
441 		liveInstances.remove(cls.getName());
442 
443 		if (RetryHandler.logger.isLoggable(Level.FINEST)) {
444 			RetryHandler.logger.exiting("RetryHandler", "unregister(Class<?>=" + cls + ")", "end");
445 		}
446 	}
447 }