public abstract class ServiceCaller extends Object
A service caller has a link to a Descriptor
instance, which
describes which back-ends to call. A Descriptor
describes
either a single back-end or a group of back-ends. A single back-end is
represented by a TargetDescriptor
instance, while a groups of
back-ends is represented by a GroupDescriptor
instance. Both are
subclasses of class Descriptor
.
There is only one type of target descriptor, but there are different types of group descriptor:
Note that group descriptors may contain other group descriptors.
Target descriptors support three kinds of time-out:
Unlike load-balancing, fail-over allows the detection of a failure and the migration of the processing to a similar, redundant back-end. This can be achieved using any type of group descriptor (either ordered or random).
Not all calls should be retried on a different back-end. For example, if
a call fails because the back-end indicates the request is considered
incorrect, then it may be considered unappropriate to try other back-ends.
The shouldFailOver
method determines whether a failed call will be retried.
Consider the following hypothetical scenario. A company has two data centers, a primary site and a secondary backup site. The primary site has 3 back-ends running an eshop service, while the hot backup site has only 2 such back-ends. The back-ends at the primary site should always be preferred over the back-ends at the backup site. At each site, load should be evenly distributed among the available back-ends within that site.
Such a scenario can be converted to a descriptor configuration such as the following:
Now if the service caller performs a call, it will first randomly select one of Main1, Main2 and Main3. If the call fails and fail-over is considered allowable, it will retry the call with one of the other back-ends in the MainSite group. If none of the back-ends in the MainSite group succeeds, it will randomly select back-ends from the BackupSite group until the call has succeeded or until all back-ends were tried.
Some aspects of a call can be configured using a CallConfig
object. For example, the CallConfig
base class indicates
whether fail-over is unconditionally allowed. Like this, some aspects of
the behaviour of the caller can be tweaked.
There are different places where a CallConfig
can be
applied:
ServiceCaller
;
CallRequest
;
First of all, each ServiceCaller
instance will have a
fall-back CallConfig
.
Secondly, a CallRequest
instance may have a
CallConfig
associated with it as well. If it does, then this
overrides the one on the ServiceCaller
instance.
Finally, a CallConfig
can be passed as an argument to the
call method. If it is, then this overrides any other settings.
This class is abstract and is intended to be have service-specific subclasses, e.g. for HTTP, FTP, JDBC, etc.
Normally, a subclass should be stick to the following rules:
Descriptor
object. This constructor should call
super(descriptor, null)
.
This descriptor should document the same exceptions as the
ServiceCaller(Descriptor,CallConfig)
constructor.
Descriptor
and a service-specific call config object
(derived from CallConfig
). This constructor should call
super(descriptor, callConfig)
.
This descriptor should document the same exceptions as the
ServiceCaller(Descriptor,CallConfig)
constructor.
isProtocolSupportedImpl(String)
should be
implemented.
call
method that accepts only a
service-specific request object (derived from CallRequest
).
It should call
doCall
(request, null)
.
call
method that accepts both a
service-specific request object (derived from CallRequest
).
and a service-specific call config object (derived from
CallConfig
). It should call
doCall
(request, callConfig)
.
doCallImpl(CallRequest,CallConfig,TargetDescriptor)
must
be implemented as specified.
createCallResult
method must be implemented as specified.
#shouldFailOver(CallRequest,CallConfig,CallExceptionList)
may also be implemented. The implementation can assume that
the passed CallRequest
object is an instance of the
service-specific call request class and that the passed
CallConfig
object is an instance of the service-specific
call config class.
Modifier | Constructor and Description |
---|---|
protected |
ServiceCaller(Descriptor descriptor,
CallConfig callConfig)
Constructs a new
ServiceCaller with the specified
CallConfig . |
Modifier and Type | Method and Description |
---|---|
protected void |
controlTimeOut(Runnable task,
TargetDescriptor descriptor)
Runs the specified task.
|
protected abstract CallResult |
createCallResult(CallRequest request,
TargetDescriptor succeededTarget,
long duration,
List<CallException> exceptions,
Object result)
Constructs an appropriate
CallResult object for a
successful call attempt. |
protected CallResult |
doCall(CallRequest request,
CallConfig callConfig)
Attempts to execute the specified call request on one of the target
services, with the specified call configuration.
|
abstract Object |
doCallImpl(CallRequest request,
CallConfig callConfig,
TargetDescriptor target)
Calls the specified target using the specified subject.
|
CallConfig |
getCallConfig()
Returns the
CallConfig associated with this service caller. |
protected abstract CallConfig |
getDefaultCallConfig()
Returns a default
CallConfig object. |
Descriptor |
getDescriptor()
Returns the descriptor.
|
boolean |
isProtocolSupported(String protocol)
Checks if the specified protocol is supported (wrapper method).
|
protected boolean |
isProtocolSupportedImpl(String protocol)
Checks if the specified protocol is supported (implementation method).
|
protected void |
setCallConfig(CallConfig config)
Sets the
CallConfig associated with this service caller. |
void |
setDescriptor(Descriptor descriptor)
Sets the descriptor.
|
protected boolean |
shouldFailOver(CallRequest request,
CallConfig callConfig,
List<CallException> exceptions)
Determines whether a call should fail-over to the next selected target
based on a request, call configuration and exception list.
|
void |
testTargetDescriptor(TargetDescriptor target)
Asserts that the specified target descriptor is considered acceptable
for this service caller.
|
protected ServiceCaller(Descriptor descriptor, CallConfig callConfig) throws UnsupportedProtocolException
ServiceCaller
with the specified
CallConfig
.
The descriptor is not mandatory. However, no calls can be made with this service caller until the descriptor is set.
descriptor
- the descriptor of the service, or null
.callConfig
- the CallConfig
object, or null
if the default
should be used.UnsupportedProtocolException
- if descriptor
is or contains a TargetDescriptor
with an unsupported protocol (since XINS 1.2.0).public final void testTargetDescriptor(TargetDescriptor target) throws IllegalArgumentException, UnsupportedProtocolException
target
- the TargetDescriptor
to test, should not be
null
.IllegalArgumentException
- if target == null
.UnsupportedProtocolException
- if the protocol in the target descriptor is unsupported.public final boolean isProtocolSupported(String protocol) throws IllegalArgumentException, UnsupportedOperationException
"://"
).
For example:
"http://www.google.nl"
, the protocol is
"http"
;
"jdbc:mysql://we.are.the.b.org/mydb/"
,
the protocol is "jdbc:mysql"
.
This method first checks the argument. If it is null
,
then an exception is thrown. Otherwise, the result of a call to
isProtocolSupportedImpl(String)
is returned, passing the
supplied protocol, but in lowercase. This method may then throw an
UnsupportedOperationException
if it is not implemented (default
behavior).
protocol
- the protocol, should not be null
.true
if the specified protocol is supported, or
false
if it is not.IllegalArgumentException
- if protocol == null
.UnsupportedOperationException
- if this method is not implemented (probably because this
ServiceCaller
implementation was originally written with
XINS 1.0.x or XINS 1.1.x)protected boolean isProtocolSupportedImpl(String protocol) throws UnsupportedOperationException
"://"
).
This method should only ever be called from the
isProtocolSupported(String)
method.
The implementation of this method in class ServiceCaller
throws an UnsupportedOperationException
.
protocol
- the protocol, guaranteed not to be null
and guaranteed
to be in lower case.true
if the specified protocol is supported, or
false
if it is not.UnsupportedOperationException
- if this method is not implemented (probably because this
ServiceCaller
implementation was originally written with
XINS 1.0.x or XINS 1.1.x)public void setDescriptor(Descriptor descriptor) throws UnsupportedProtocolException
descriptor
- the descriptor for this service, or null
.UnsupportedProtocolException
- if descriptor
is or contains a TargetDescriptor
with an unsupported protocol.public final Descriptor getDescriptor()
null
is returned.
Since XINS 1.2.0, this method may return null
.
null
if it is
currently unset.protected final void setCallConfig(CallConfig config) throws IllegalArgumentException
CallConfig
associated with this service caller.
This method should only be called on new-style (XINS 1.1) service
callers that used the ServiceCaller(Descriptor,CallConfig)
constructor.
config
- the fall-back CallConfig
object for this service caller,
cannot be null
.IllegalArgumentException
- if config == null
.public final CallConfig getCallConfig()
CallConfig
associated with this service caller.CallConfig
object for this service caller,
never null
.protected abstract CallConfig getDefaultCallConfig()
CallConfig
object. This method is called
by the ServiceCaller
constructor if no
CallConfig
object was given.
Subclasses that support the new service calling framework (introduced
in XINS 1.1.0) must override this method to return a more
suitable CallConfig
instance.
This method should never be called by subclasses.
CallConfig
instance, never
null
.protected final CallResult doCall(CallRequest request, CallConfig callConfig) throws IllegalArgumentException, IllegalStateException, CallException
Target descriptors
will be picked and passed to
doCallImpl(CallRequest,CallConfig,TargetDescriptor)
until there
is one that succeeds, as long as fail-over can be done (according to
#shouldFailOver(CallRequest,CallConfig,CallExceptionList)
).
If one of the calls succeeds, then the result is returned. If
none succeeds or if fail-over should not be done, then a
CallException
is thrown.
Subclasses that want to use this method must implement
doCallImpl(CallRequest,CallConfig,TargetDescriptor)
. That
method is called for each call attempt to a specific service target
(represented by a TargetDescriptor
).
request
- the call request, not null
.callConfig
- the call configuration, or null
if the one defined for
the call request should be used if specified, or otherwise the
fall-back call configuration associated with this
ServiceCaller
(see getCallConfig()
).target
that returned this result, if and
only if one of the calls succeeded, could be null
.IllegalArgumentException
- if request == null
.IllegalStateException
- if the descriptor is currently unset (since XINS 1.2.0).CallException
- if all call attempts failed.public abstract Object doCallImpl(CallRequest request, CallConfig callConfig, TargetDescriptor target) throws ClassCastException, IllegalArgumentException, CallException
CallException
should be thrown. If the call succeeds, then the call result should be
returned from this method.
Subclasses that want to use doCall(CallRequest,CallConfig)
must implement this method.
request
- the call request to be executed, never null
.callConfig
- the call config to be used, never null
; this is
determined by doCall(CallRequest,CallConfig)
and is
guaranteed not to be null
.target
- the target to call, cannot be null
.null
.ClassCastException
- if the specified request
object is not null
and not an instance of an expected subclass of class
CallRequest
.IllegalArgumentException
- if target == null || request == null
.CallException
- if the call to the specified target failed.protected abstract CallResult createCallResult(CallRequest request, TargetDescriptor succeededTarget, long duration, List<CallException> exceptions, Object result) throws ClassCastException
CallResult
object for a
successful call attempt. This method is called from
doCall(CallRequest,CallConfig)
.request
- the CallRequest
that was to be executed, never
null
when called from doCall(CallRequest,CallConfig)
.succeededTarget
- the TargetDescriptor
for the service that was successfully
called, never null
when called from
doCall(CallRequest,CallConfig)
.duration
- the call duration in milliseconds, guaranteed to be a non-negative
number when called from doCall(CallRequest,CallConfig)
.exceptions
- the list of CallException
instances, or null
if
there were no call failures.result
- the result from the call, which is the object returned by
doCallImpl(CallRequest,CallConfig,TargetDescriptor)
, can be
null
.CallResult
instance, never null
.ClassCastException
- if request
and/or result
are not of the
correct class.protected final void controlTimeOut(Runnable task, TargetDescriptor descriptor) throws IllegalArgumentException, IllegalThreadStateException, SecurityException, TimeOutException
Thread.interrupt()
method and a TimeOutException
is
thrown.task
- the task to run, cannot be null
.descriptor
- the descriptor for the target on which the task is executed, cannot
be null
.IllegalArgumentException
- if task == null || descriptor == null
.IllegalThreadStateException
- if descriptor.getTotalTimeOut() > 0
and the task is a
Thread
which is already started.SecurityException
- if the task did not finish within the total time-out period, but the
interruption of the thread was disallowed (see
Thread.interrupt()
).TimeOutException
- if the task did not finish within the total time-out period and was
interrupted.protected boolean shouldFailOver(CallRequest request, CallConfig callConfig, List<CallException> exceptions)
doCall(CallRequest,CallConfig)
.
This method is typically overridden by subclasses. Usually, a
subclass first calls this method in the superclass, and if that returns
false
it does some additional checks, otherwise
true
is immediately returned.
The implementation of this method in class ServiceCaller
returns true
if and only if at least one of the following
conditions is true:
callConfig.isFailOverAllowed()
exception instanceof ConnectionCallException
request
- the request for the call, as passed to doCall(CallRequest,CallConfig)
,
should not be null
.callConfig
- the call config that is currently in use, never null
.exceptions
- the current list of CallException
s; never
null
; get the most recent one by calling
exceptions.
.true
if the call should fail-over to the next target, or
false
if it should not.See http://www.xins.org/.