001: /*
002: * $Id: ServiceCaller.java,v 1.76 2007/03/15 17:08:27 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.service;
008:
009: import java.util.Iterator;
010:
011: import org.xins.common.Log;
012: import org.xins.common.MandatoryArgumentChecker;
013: import org.xins.common.TimeOutController;
014: import org.xins.common.TimeOutException;
015: import org.xins.common.Utils;
016:
017: /**
018: * Abstraction of a service caller for a TCP-based service. Service caller
019: * implementations can be used to perform a call to a service, and potentially
020: * fail-over to other back-ends if one is not available.
021: *
022: * <a name="section-descriptors"></a>
023: * <h2>Descriptors</h2>
024: *
025: * <p>A service caller has a link to a {@link Descriptor} instance, which
026: * describes which back-ends to call. A <code>Descriptor</code> describes
027: * either a single back-end or a group of back-ends. A single back-end is
028: * represented by a {@link TargetDescriptor} instance, while a groups of
029: * back-ends is represented by a {@link GroupDescriptor} instance. Both are
030: * subclasses of class <code>Descriptor</code>.
031: *
032: * <p>There is only one type of target descriptor, but there are
033: * different types of group descriptor:
034: *
035: * <ul>
036: * <li><em>ordered</em>: underlying descriptors are iterated over in
037: * sequential order;
038: * <li><em>random</em>: underlying descriptors are iterated over in random
039: * order.
040: * </ul>
041: *
042: * <p>Note that group descriptors may contain other group descriptors.
043: *
044: * <a name="section-timeouts"></a>
045: * <h2>Time-outs</h2>
046: *
047: * <p>Target descriptors support three kinds of time-out:
048: *
049: * <ul>
050: * <li><em>total time-out</em>: limits the duration of a call,
051: * including connection time, time used to send the request, time used to
052: * receive the response, etcetera;
053: * <li><em>connection time-out</em>: limits the time for attempting to
054: * establish a connection;
055: * <li><em>socket time-out</em>: limits the time for attempting to receive
056: * data on a socket.
057: * </ul>
058: *
059: * <a name="section-lbfo"></a>
060: * <h2>Load-balancing and fail-over</h2>
061: *
062: * Service callers can help in evenly distributing processing across
063: * available resources. This load-balancing is achieved by using a group
064: * descriptor which iterates over the underlying descriptors in a
065: * <em>random</em> order.
066: *
067: * <p>Unlike load-balancing, fail-over allows the detection of a failure and
068: * the migration of the processing to a similar, redundant back-end. This can
069: * be achieved using any type of group descriptor (either <em>ordered</em> or
070: * <em>random</em>).
071: *
072: * <p>Not all calls should be retried on a different back-end. For example, if
073: * a call fails because the back-end indicates the request is considered
074: * incorrect, then it may be considered unappropriate to try other back-ends.
075: * The {@link #shouldFailOver(CallRequest,CallConfig,CallExceptionList)
076: * shouldFailOver} method determines whether a failed call will be retried.
077: *
078: * <p>Consider the following hypothetical scenario. A company has two data
079: * centers, a primary site and a secondary backup site. The primary site has
080: * 3 back-ends running an <em>eshop</em> service, while the hot backup site
081: * has only 2 such back-ends. The back-ends at the primary site should always
082: * be preferred over the back-ends at the backup site. At each site, load
083: * should be evenly distributed among the available back-ends within that
084: * site.
085: *
086: * <p>Such a scenario can be converted to a descriptor configuration such as
087: * the following:
088: *
089: * <ul>
090: * <li>the service caller uses a group descriptor called <em>All</em> of type
091: * <em>ordered</em>. This group descriptor contains 2 other group
092: * descriptors: <em>MainSite</em> and <em>BackupSite</em>.
093: * <li>the group descriptor <em>MainSite</em> is of type <em>random</em> and
094: * contains 3 target descriptors, called <em>Main1</em>,
095: * <em>Main2</em> and <em>Main3</em>.
096: * <li>the group descriptor <em>BackupSite</em> is also of type
097: * <em>random</em> and contains 2 target descriptors, called
098: * <em>Backup1</em> and <em>Backup2</em>.
099: * </ul>
100: *
101: * <p>Now if the service caller performs a call, it will first randomly select
102: * one of <em>Main1</em>, <em>Main2</em> and <em>Main3</em>. If the call
103: * fails and fail-over is considered allowable, it will retry the call with
104: * one of the other back-ends in the <em>MainSite</em> group. If none of the
105: * back-ends in the <em>MainSite</em> group succeeds, it will randomly select
106: * back-ends from the <em>BackupSite</em> group until the call has succeeded
107: * or until all back-ends were tried.
108: *
109: * <a name="section-callconfig"></a>
110: * <h2>Call configuration</h2>
111: *
112: * <p>Some aspects of a call can be configured using a {@link CallConfig}
113: * object. For example, the <code>CallConfig</code> base class indicates
114: * whether fail-over is unconditionally allowed. Like this, some aspects of
115: * the behaviour of the caller can be tweaked.
116: *
117: * <p>There are different places where a <code>CallConfig</code> can be
118: * applied:
119: *
120: * <ul>
121: * <li>associated with a <code>ServiceCaller</code>;
122: * <li>associated with a <code>CallRequest</code>;
123: * <li>passed with the call method.
124: * </ul>
125: *
126: * <p>First of all, each <code>ServiceCaller</code> instance will have a
127: * fall-back <code>CallConfig</code>.
128: *
129: * <p>Secondly, a {@link CallRequest} instance may have a
130: * <code>CallConfig</code> associated with it as well. If it does, then this
131: * overrides the one on the <code>ServiceCaller</code> instance.
132: *
133: * <p>Finally, a <code>CallConfig</code> can be passed as an argument to the
134: * call method. If it is, then this overrides any other settings.
135: *
136: * <a name="section-implementations"></a>
137: * <h2>Subclass implementations</h2>
138: *
139: * <p>This class is abstract and is intended to be have service-specific
140: * subclasses, e.g. for HTTP, FTP, JDBC, etc.
141: *
142: * <p>Normally, a subclass should be stick to the following rules:
143: *
144: * <ol>
145: * <li>There should be a constructor that accepts only a {@link Descriptor}
146: * object. This constructor should call
147: * <code>super(descriptor, null)</code>.
148: * This descriptor should document the same exceptions as the
149: * {@link #ServiceCaller(Descriptor,CallConfig)} constructor.
150: * <li>There should be a constructor that accepts both a
151: * {@link Descriptor} and a service-specific call config object
152: * (derived from {@link CallConfig}). This constructor should call
153: * <code>super(descriptor, callConfig)</code>.
154: * This descriptor should document the same exceptions as the
155: * {@link #ServiceCaller(Descriptor,CallConfig)} constructor.
156: * <li>The method {@link #isProtocolSupportedImpl(String)} should be
157: * implemented.
158: * <li>There should be a <code>call</code> method that accepts only a
159: * service-specific request object (derived from {@link CallRequest}).
160: * It should call
161: * {@link #doCall(CallRequest,CallConfig) doCall}<code>(request, null)</code>.
162: * <li>There should be a <code>call</code> method that accepts both a
163: * service-specific request object (derived from {@link CallRequest}).
164: * and a service-specific call config object (derived from
165: * {@link CallConfig}). It should call
166: * {@link #doCall(CallRequest,CallConfig) doCall}<code>(request, callConfig)</code>.
167: * <li>The method
168: * {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)} must
169: * be implemented as specified.
170: * <li>The {@link #createCallResult(CallRequest,TargetDescriptor,long,CallExceptionList,Object) createCallResult}
171: * method must be implemented as specified.
172: * <li>To control when fail-over is applied, the method
173: * {@link #shouldFailOver(CallRequest,CallConfig,CallExceptionList)}
174: * may also be implemented. The implementation can assume that
175: * the passed {@link CallRequest} object is an instance of the
176: * service-specific call request class and that the passed
177: * {@link CallConfig} object is an instance of the service-specific
178: * call config class.
179: * </ol>
180: *
181: * @version $Revision: 1.76 $ $Date: 2007/03/15 17:08:27 $
182: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
183: *
184: * @since XINS 1.0.0
185: */
186: public abstract class ServiceCaller {
187:
188: /**
189: * The descriptor for this service. Can be <code>null</code>.
190: */
191: private Descriptor _descriptor;
192:
193: /**
194: * The fall-back call config object for this service caller. Can only be
195: * <code>null</code> if this is an old-style service caller.
196: */
197: private CallConfig _callConfig;
198:
199: /**
200: * Constructs a new <code>ServiceCaller</code> with the specified
201: * <code>CallConfig</code>.
202: *
203: * <p>The descriptor is not mandatory. However, no calls can be made with
204: * this service caller until the descriptor is set.
205: *
206: * @param descriptor
207: * the descriptor of the service, or <code>null</code>.
208: *
209: * @param callConfig
210: * the {@link CallConfig} object, or <code>null</code> if the default
211: * should be used.
212: *
213: * @throws UnsupportedProtocolException
214: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
215: * with an unsupported protocol (<em>since XINS 1.2.0</em>).
216: *
217: * @since XINS 1.1.0
218: */
219: protected ServiceCaller(Descriptor descriptor, CallConfig callConfig)
220: throws UnsupportedProtocolException {
221:
222: // Store information
223: setDescriptor(descriptor);
224:
225: // If no CallConfig is specified, then use a default one
226: if (callConfig == null) {
227:
228: // Call getDefaultCallConfig() to get the default config...
229: try {
230: callConfig = getDefaultCallConfig();
231:
232: // ...it should not throw any exception...
233: } catch (Throwable t) {
234: throw Utils.logProgrammingError(t);
235: }
236:
237: // ...and it should never return null.
238: if (callConfig == null) {
239: throw Utils
240: .logProgrammingError("Method returned null, although that is disallowed by the ServiceCaller.getDefaultCallConfig() contract.");
241: }
242: }
243:
244: // Set call configuration
245: _callConfig = callConfig;
246: }
247:
248: /**
249: * Asserts that the specified target descriptor is considered acceptable
250: * for this service caller. If not, an exception is thrown.
251: *
252: * @param target
253: * the {@link TargetDescriptor} to test, should not be
254: * <code>null</code>.
255: *
256: * @throws IllegalArgumentException
257: * if <code>target == null</code>.
258: *
259: * @throws UnsupportedProtocolException
260: * if the protocol in the target descriptor is unsupported.
261: *
262: * @since XINS 1.2.0
263: */
264: public final void testTargetDescriptor(TargetDescriptor target)
265: throws IllegalArgumentException,
266: UnsupportedProtocolException {
267:
268: // Check preconditions
269: MandatoryArgumentChecker.check("target", target);
270:
271: try {
272: if (!isProtocolSupported(target.getProtocol())) {
273: throw new UnsupportedProtocolException(target);
274: }
275: } catch (UnsupportedOperationException exception) {
276: // ignore
277: }
278: }
279:
280: /**
281: * Checks if the specified protocol is supported (wrapper method). The
282: * protocol is the part in a URL before the string <code>"://"</code>).
283: *
284: * <p>For example:
285: *
286: * <ul>
287: * <li>in the URL <code>"http://www.google.nl"</code>, the protocol is
288: * <code>"http"</code>;
289: *
290: * <li>in the URL <code>"jdbc:mysql://we.are.the.b.org/mydb/"</code>,
291: * the protocol is <code>"jdbc:mysql"</code>.
292: * </ul>
293: *
294: * <p>This method first checks the argument. If it is <code>null</code>,
295: * then an exception is thrown. Otherwise, the result of a call to
296: * {@link #isProtocolSupportedImpl(String)} is returned, passing the
297: * supplied protocol, but in lowercase. This method may then throw an
298: * {@link UnsupportedOperationException} if it is not implemented (default
299: * behavior).
300: *
301: * @param protocol
302: * the protocol, should not be <code>null</code>.
303: *
304: * @return
305: * <code>true</code> if the specified protocol is supported, or
306: * <code>false</code> if it is not.
307: *
308: * @throws IllegalArgumentException
309: * if <code>protocol == null</code>.
310: *
311: * @throws UnsupportedOperationException
312: * if this method is not implemented (probably because this
313: * <code>ServiceCaller</code> implementation was originally written with
314: * XINS 1.0.x or XINS 1.1.x)
315: *
316: * @since XINS 1.2.0
317: */
318: public final boolean isProtocolSupported(String protocol)
319: throws IllegalArgumentException,
320: UnsupportedOperationException {
321:
322: // Check preconditions
323: MandatoryArgumentChecker.check("protocol", protocol);
324:
325: return isProtocolSupportedImpl(protocol.toLowerCase());
326: }
327:
328: /**
329: * Checks if the specified protocol is supported (implementation method).
330: * The protocol is the part in a URL before the string <code>"://"</code>).
331: *
332: * <p>This method should only ever be called from the
333: * {@link #isProtocolSupported(String)} method.
334: *
335: * <p>The implementation of this method in class <code>ServiceCaller</code>
336: * throws an {@link UnsupportedOperationException}.
337: *
338: * @param protocol
339: * the protocol, guaranteed not to be <code>null</code> and guaranteed
340: * to be in lower case.
341: *
342: * @return
343: * <code>true</code> if the specified protocol is supported, or
344: * <code>false</code> if it is not.
345: *
346: * @throws UnsupportedOperationException
347: * if this method is not implemented (probably because this
348: * <code>ServiceCaller</code> implementation was originally written with
349: * XINS 1.0.x or XINS 1.1.x)
350: *
351: * @since XINS 1.2.0
352: */
353: protected boolean isProtocolSupportedImpl(String protocol)
354: throws UnsupportedOperationException {
355: throw new UnsupportedOperationException();
356: }
357:
358: /**
359: * Sets the descriptor.
360: *
361: * @param descriptor
362: * the descriptor for this service, or <code>null</code>.
363: *
364: * @throws UnsupportedProtocolException
365: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
366: * with an unsupported protocol.
367: *
368: * @since XINS 1.2.0
369: */
370: public void setDescriptor(Descriptor descriptor)
371: throws UnsupportedProtocolException {
372:
373: // Test the protocol for all TargetDescriptors
374: if (descriptor != null) {
375: Iterator targets = descriptor.iterateTargets();
376: while (targets.hasNext()) {
377: testTargetDescriptor((TargetDescriptor) targets.next());
378: }
379: }
380:
381: // Store it
382: _descriptor = descriptor;
383: }
384:
385: /**
386: * Returns the descriptor. If the descriptor is currently unset, then
387: * <code>null</code> is returned.
388: *
389: * <p><em>Since XINS 1.2.0, this method may return <code>null</code>.</em>
390: *
391: * @return
392: * the descriptor for this service, or <code>null</code> if it is
393: * currently unset.
394: */
395: public final Descriptor getDescriptor() {
396: return _descriptor;
397: }
398:
399: /**
400: * Sets the <code>CallConfig</code> associated with this service caller.
401: *
402: * <p>This method should only be called on new-style (XINS 1.1) service
403: * callers that used the {@link #ServiceCaller(Descriptor,CallConfig)}
404: * constructor.
405: *
406: * @param config
407: * the fall-back {@link CallConfig} object for this service caller,
408: * cannot be <code>null</code>.
409: *
410: * @throws IllegalArgumentException
411: * if <code>config == null</code>.
412: *
413: * @since XINS 1.2.0
414: */
415: protected final void setCallConfig(CallConfig config)
416: throws IllegalArgumentException {
417:
418: // Check argument
419: MandatoryArgumentChecker.check("config", config);
420:
421: _callConfig = config;
422: }
423:
424: /**
425: * Returns the <code>CallConfig</code> associated with this service caller.
426: *
427: * @return
428: * the fall-back {@link CallConfig} object for this service caller,
429: * never <code>null</code>.
430: *
431: * @since XINS 1.1.0
432: */
433: public final CallConfig getCallConfig() {
434: return _callConfig;
435: }
436:
437: /**
438: * Returns a default <code>CallConfig</code> object. This method is called
439: * by the <code>ServiceCaller</code> constructor if no
440: * <code>CallConfig</code> object was given.
441: *
442: * <p>Subclasses that support the new service calling framework (introduced
443: * in XINS 1.1.0) <em>must</em> override this method to return a more
444: * suitable <code>CallConfig</code> instance.
445: *
446: * <p>This method should never be called by subclasses.
447: *
448: * @return
449: * a new, appropriate, {@link CallConfig} instance, never
450: * <code>null</code>.
451: *
452: * @since XINS 1.1.0
453: */
454: protected abstract CallConfig getDefaultCallConfig();
455:
456: /**
457: * Attempts to execute the specified call request on one of the target
458: * services, with the specified call configuration. During the execution,
459: * {@link TargetDescriptor Target descriptors} will be picked and passed to
460: * {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)} until there
461: * is one that succeeds, as long as fail-over can be done (according to
462: * {@link #shouldFailOver(CallRequest,CallConfig,CallExceptionList)}).
463: *
464: * <p>If one of the calls succeeds, then the result is returned. If
465: * none succeeds or if fail-over should not be done, then a
466: * {@link CallException} is thrown.
467: *
468: * <p>Subclasses that want to use this method <em>must</em> implement
469: * {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)}. That
470: * method is called for each call attempt to a specific service target
471: * (represented by a {@link TargetDescriptor}).
472: *
473: * @param request
474: * the call request, not <code>null</code>.
475: *
476: * @param callConfig
477: * the call configuration, or <code>null</code> if the one defined for
478: * the call request should be used if specified, or otherwise the
479: * fall-back call configuration associated with this
480: * <code>ServiceCaller</code> (see {@link #getCallConfig()}).
481: *
482: * @return
483: * a combination of the call result and a link to the
484: * {@link TargetDescriptor target} that returned this result, if and
485: * only if one of the calls succeeded, could be <code>null</code>.
486: *
487: * @throws IllegalArgumentException
488: * if <code>request == null</code>.
489: *
490: * @throws IllegalStateException
491: * if the descriptor is currently unset (<em>since XINS 1.2.0</em>).
492: *
493: * @throws CallException
494: * if all call attempts failed.
495: *
496: * @since XINS 1.1.0
497: */
498: protected final CallResult doCall(CallRequest request,
499: CallConfig callConfig) throws IllegalArgumentException,
500: IllegalStateException, CallException {
501:
502: // Check preconditions
503: MandatoryArgumentChecker.check("request", request);
504:
505: // Determine descriptor
506: Descriptor descriptor = _descriptor;
507: if (descriptor == null) {
508: throw new IllegalStateException(
509: "Descriptor is currently unset.");
510: }
511:
512: // Determine what config to use. The argument has priority, then the one
513: // associated with the request and the fall-back is the one associated
514: // with this service caller.
515: if (callConfig == null) {
516: callConfig = request.getCallConfig();
517: if (callConfig == null) {
518: callConfig = _callConfig;
519: }
520: }
521:
522: // Keep a reference to the most recent CallException since
523: // setNext(CallException) needs to be called on it to make it link to
524: // the next one (if there is one)
525: CallException lastException = null;
526:
527: // Maintain the list of CallExceptions
528: //
529: // This is needed if a successful result (a CallResult object) is
530: // returned, since it will contain references to the exceptions as well;
531: //
532: // Note that this object is lazily initialized because this code is
533: // performance- and memory-optimized for the successful case
534: CallExceptionList exceptions = null;
535:
536: // Iterate over all targets
537: Iterator iterator = descriptor.iterateTargets();
538:
539: // There should be at least one target
540: if (!iterator.hasNext()) {
541: throw Utils
542: .logProgrammingError("Descriptor returns no target descriptors.");
543: }
544:
545: // Loop over all TargetDescriptors
546: boolean shouldContinue = true;
547: while (shouldContinue) {
548:
549: // Get a reference to the next TargetDescriptor
550: TargetDescriptor target = (TargetDescriptor) iterator
551: .next();
552:
553: // Call using this target
554: Log.log_1301(target.getURL());
555: Object result = null;
556: boolean succeeded = false;
557: long start = System.currentTimeMillis();
558: try {
559:
560: // Attempt the call
561: result = doCallImpl(request, callConfig, target);
562: succeeded = true;
563:
564: // If the call to the target fails, store the exception and try the next
565: } catch (Throwable exception) {
566:
567: Log.log_1302(target.getURL());
568:
569: long duration = System.currentTimeMillis() - start;
570:
571: // If the caught exception is not a CallException, then
572: // encapsulate it in one
573: CallException currentException;
574: if (exception instanceof CallException) {
575: currentException = (CallException) exception;
576: } else {
577: currentException = new UnexpectedExceptionCallException(
578: request, target, duration, null, exception);
579: }
580:
581: // Link the previous exception (if there is one) to this one
582: if (lastException != null) {
583: lastException.setNext(currentException);
584: }
585:
586: // Now set this exception as the most recent CallException
587: lastException = currentException;
588:
589: // If this is the first exception being caught, then lazily
590: // initialize the CallExceptionList and keep a reference to the
591: // first exception
592: if (exceptions == null) {
593: exceptions = new CallExceptionList();
594: }
595:
596: // Store the failure
597: exceptions.add(currentException);
598:
599: // Determine whether fail-over is allowed and whether we have
600: // another target to fail-over to
601: boolean failOver = shouldFailOver(request, callConfig,
602: exceptions);
603: boolean haveNext = iterator.hasNext();
604:
605: // No more targets and no fail-over
606: if (!haveNext && !failOver) {
607: Log.log_1304();
608: shouldContinue = false;
609:
610: // No more targets but fail-over would be allowed
611: } else if (!haveNext) {
612: Log.log_1305();
613: shouldContinue = false;
614:
615: // More targets available but fail-over is not allowed
616: } else if (!failOver) {
617: Log.log_1306();
618: shouldContinue = false;
619:
620: // More targets available and fail-over is allowed
621: } else {
622: Log.log_1307();
623: shouldContinue = true;
624: }
625: }
626:
627: // The call succeeded
628: if (succeeded) {
629: long duration = System.currentTimeMillis() - start;
630:
631: return createCallResult(request, target, duration,
632: exceptions, result);
633: }
634: }
635:
636: // Loop ended, call failed completely
637: Log.log_1303();
638:
639: // Get the first exception from the list, this one should be thrown
640: CallException first = exceptions.get(0);
641:
642: throw first;
643: }
644:
645: /**
646: * Calls the specified target using the specified subject. This method must
647: * be implemented by subclasses. It is called as soon as a target is
648: * selected to be called. If the call fails, then a {@link CallException}
649: * should be thrown. If the call succeeds, then the call result should be
650: * returned from this method.
651: *
652: * <p>Subclasses that want to use {@link #doCall(CallRequest,CallConfig)}
653: * <em>must</em> implement this method.
654: *
655: * @param request
656: * the call request to be executed, never <code>null</code>.
657: *
658: * @param callConfig
659: * the call config to be used, never <code>null</code>; this is
660: * determined by {@link #doCall(CallRequest,CallConfig)} and is
661: * guaranteed not to be <code>null</code>.
662: *
663: * @param target
664: * the target to call, cannot be <code>null</code>.
665: *
666: * @return
667: * the result, if and only if the call succeeded, could be
668: * <code>null</code>.
669: *
670: * @throws ClassCastException
671: * if the specified <code>request</code> object is not <code>null</code>
672: * and not an instance of an expected subclass of class
673: * {@link CallRequest}.
674: *
675: * @throws IllegalArgumentException
676: * if <code>target == null || request == null</code>.
677: *
678: * @throws CallException
679: * if the call to the specified target failed.
680: *
681: * @since XINS 1.1.0
682: */
683: public abstract Object doCallImpl(CallRequest request,
684: CallConfig callConfig, TargetDescriptor target)
685: throws ClassCastException, IllegalArgumentException,
686: CallException;
687:
688: /**
689: * Constructs an appropriate <code>CallResult</code> object for a
690: * successful call attempt. This method is called from
691: * {@link #doCall(CallRequest,CallConfig)}.
692: *
693: * @param request
694: * the {@link CallRequest} that was to be executed, never
695: * <code>null</code> when called from {@link #doCall(CallRequest,CallConfig)}.
696: *
697: * @param succeededTarget
698: * the {@link TargetDescriptor} for the service that was successfully
699: * called, never <code>null</code> when called from
700: * {@link #doCall(CallRequest,CallConfig)}.
701: *
702: * @param duration
703: * the call duration in milliseconds, guaranteed to be a non-negative
704: * number when called from {@link #doCall(CallRequest,CallConfig)}.
705: *
706: * @param exceptions
707: * the list of {@link CallException} instances, or <code>null</code> if
708: * there were no call failures.
709: *
710: * @param result
711: * the result from the call, which is the object returned by
712: * {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)}, can be
713: * <code>null</code>.
714: *
715: * @return
716: * a {@link CallResult} instance, never <code>null</code>.
717: *
718: * @throws ClassCastException
719: * if <code>request</code> and/or <code>result</code> are not of the
720: * correct class.
721: */
722: protected abstract CallResult createCallResult(CallRequest request,
723: TargetDescriptor succeededTarget, long duration,
724: CallExceptionList exceptions, Object result)
725: throws ClassCastException;
726:
727: /**
728: * Runs the specified task. If the task does not finish within the total
729: * time-out period, then the thread executing it is interrupted using the
730: * {@link Thread#interrupt()} method and a {@link TimeOutException} is
731: * thrown.
732: *
733: * @param task
734: * the task to run, cannot be <code>null</code>.
735: *
736: * @param descriptor
737: * the descriptor for the target on which the task is executed, cannot
738: * be <code>null</code>.
739: *
740: * @throws IllegalArgumentException
741: * if <code>task == null || descriptor == null</code>.
742: *
743: * @throws IllegalThreadStateException
744: * if <code>descriptor.getTotalTimeOut() > 0</code> and the task is a
745: * {@link Thread} which is already started.
746: *
747: * @throws SecurityException
748: * if the task did not finish within the total time-out period, but the
749: * interruption of the thread was disallowed (see
750: * {@link Thread#interrupt()}).
751: *
752: * @throws TimeOutException
753: * if the task did not finish within the total time-out period and was
754: * interrupted.
755: */
756: protected final void controlTimeOut(Runnable task,
757: TargetDescriptor descriptor)
758: throws IllegalArgumentException,
759: IllegalThreadStateException, SecurityException,
760: TimeOutException {
761:
762: // Check preconditions
763: MandatoryArgumentChecker.check("task", task, "descriptor",
764: descriptor);
765:
766: // Determine the total time-out
767: int totalTimeOut = descriptor.getTotalTimeOut();
768:
769: // If there is no total time-out, then execute the task on this thread
770: if (totalTimeOut < 1) {
771: task.run();
772:
773: // Otherwise a time-out controller will be used
774: } else {
775: TimeOutController.execute(task, totalTimeOut);
776: }
777: }
778:
779: /**
780: * Determines whether a call should fail-over to the next selected target
781: * based on a request, call configuration and exception list.
782: * This method should only be called from
783: * {@link #doCall(CallRequest,CallConfig)}.
784: *
785: * <p>This method is typically overridden by subclasses. Usually, a
786: * subclass first calls this method in the superclass, and if that returns
787: * <code>false</code> it does some additional checks, otherwise
788: * <code>true</code> is immediately returned.
789: *
790: * <p>The implementation of this method in class {@link ServiceCaller}
791: * returns <code>true</code> if and only if at least one of the following
792: * conditions is true:
793: *
794: * <ul>
795: * <li><code>callConfig.{@link CallConfig#isFailOverAllowed()
796: * isFailOverAllowed()}</code>
797: * <li><code>exception instanceof {@link ConnectionCallException}</code>
798: * </ul>
799: *
800: * @param request
801: * the request for the call, as passed to {@link #doCall(CallRequest,CallConfig)},
802: * should not be <code>null</code>.
803: *
804: * @param callConfig
805: * the call config that is currently in use, never <code>null</code>.
806: *
807: * @param exceptions
808: * the current list of {@link CallException}s; never
809: * <code>null</code>; get the most recent one by calling
810: * <code>exceptions.</code>{@link CallExceptionList#last() last()}.
811: *
812: * @return
813: * <code>true</code> if the call should fail-over to the next target, or
814: * <code>false</code> if it should not.
815: *
816: * @since XINS 1.1.0
817: */
818: protected boolean shouldFailOver(CallRequest request,
819: CallConfig callConfig, CallExceptionList exceptions) {
820: MandatoryArgumentChecker.check("request", request,
821: "callConfig", callConfig, "exceptions", exceptions);
822:
823: // Determine if fail-over is applicable
824: boolean should = callConfig.isFailOverAllowed()
825: || (exceptions.last() instanceof ConnectionCallException);
826:
827: return should;
828: }
829: }
|