org.xins.client.XINSServiceCaller Maven / Gradle / Ivy
/*
* $Id: XINSServiceCaller.java,v 1.192 2012/04/16 19:40:39 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.client;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.xins.common.FormattedParameters;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.http.HTTPCallConfig;
import org.xins.common.http.HTTPCallException;
import org.xins.common.http.HTTPCallRequest;
import org.xins.common.http.HTTPCallResult;
import org.xins.common.http.HTTPServiceCaller;
import org.xins.common.http.StatusCodeHTTPCallException;
import org.xins.common.service.CallConfig;
import org.xins.common.service.CallException;
import org.xins.common.service.CallRequest;
import org.xins.common.service.CallResult;
import org.xins.common.service.ConnectionTimeOutCallException;
import org.xins.common.service.ConnectionRefusedCallException;
import org.xins.common.service.Descriptor;
import org.xins.common.service.GenericCallException;
import org.xins.common.service.IOCallException;
import org.xins.common.service.ServiceCaller;
import org.xins.common.service.SocketTimeOutCallException;
import org.xins.common.service.TargetDescriptor;
import org.xins.common.service.TotalTimeOutCallException;
import org.xins.common.service.UnexpectedExceptionCallException;
import org.xins.common.service.UnknownHostCallException;
import org.xins.common.service.UnsupportedProtocolException;
import org.xins.common.spec.ErrorCodeSpec;
import org.xins.common.text.ParseException;
import org.xins.common.text.TextUtils;
import org.w3c.dom.Element;
import org.xins.common.http.*;
import org.znerd.util.ExceptionUtils;
/**
* XINS service caller. This class can be used to perform a call to a XINS
* service, over HTTP, and fail-over to other XINS services if the first one
* fails.
*
* Supported protocols
*
* This service caller currently only supports the HTTP protocol. If a
* {@link TargetDescriptor} is passed to the constructor with a different
* protocol, then an {@link UnsupportedProtocolException} is thrown. In the
* future, HTTPS and other protocols are expected to be supported as well.
*
*
Load-balancing and fail-over
*
* To perform a XINS call, use {@link #call(XINSCallRequest)}. Fail-over
* and load-balancing can be performed automatically.
*
*
How load-balancing is done depends on the {@link Descriptor} passed to
* the {@link #XINSServiceCaller(Descriptor)} constructor. If it is a
* {@link TargetDescriptor}, then only this single target service is called
* and no load-balancing is performed. If it is a
* {@link org.xins.common.service.GroupDescriptor}, then the configuration of
* the GroupDescriptor
determines how the load-balancing is done.
* A GroupDescriptor
is a recursive data structure, which allows
* for fairly advanced load-balancing algorithms.
*
*
If a call attempt fails and there are more available target services,
* then the XINSServiceCaller
may or may not fail-over to a next
* target. If the request was not accepted by the target service, then
* fail-over is considered acceptable and will be performed. This includes
* the following situations:
*
*
* - if the failOverAllowed property is set to
true
* for the {@link XINSCallRequest};
* - on connection refusal;
*
- if a connection attempt times out;
*
- if an HTTP status code other than 200-299 is returned;
*
- if the XINS error code _InvalidRequest is returned;
*
- if the XINS error code _DisabledFunction is returned.
*
*
* If none of these conditions holds, then fail-over is not considered
* acceptable and will not be performed.
*
*
Example code
*
* The following example code snippet constructs a
* XINSServiceCaller
instance:
*
*
// Initialize properties for the services. Normally these
// properties would come from a configuration source, like a file.
{@link java.util.Map} properties = new {@link java.util.HashMap}();
properties.{@link java.util.Map#put(Object,Object) put}("myapi", "group, random, server1, server2");
properties.{@link java.util.Map#put(Object,Object) put}("myapi.server1", "service, http://server1/myapi, 10000");
properties.{@link java.util.Map#put(Object,Object) put}("myapi.server2", "service, http://server2/myapi, 12000");
// Construct a descriptor and a XINSServiceCaller instance
{@link Descriptor Descriptor} descriptor = {@link org.xins.common.service.DescriptorBuilder DescriptorBuilder}.{@link org.xins.common.service.DescriptorBuilder#build(Map,String) build}(properties, "myapi");
XINSServiceCaller caller = new {@link #XINSServiceCaller(Descriptor) XINSServiceCaller}(descriptor);
*
* Then the following code snippet uses this XINSServiceCaller
* to perform a call to a XINS function named _GetStatistics, using
* HTTP POST:
*
*
// Prepare for the call
{@link String} function = "_GetStatistics";
{@link java.util.Map} params = null;
boolean failOver = true;
{@link org.xins.common.http.HTTPMethod} method = {@link org.xins.common.http.HTTPMethod}.{@link org.xins.common.http.HTTPMethod#POST POST};
{@link XINSCallRequest} request = new {@link XINSCallRequest#XINSCallRequest(String,Map,boolean,HTTPMethod) XINSCallRequest}(function, params, failOver, method);
// Perform the call
{@link XINSCallResult} result = caller.{@link #call(XINSCallRequest) call}(request);
*
* @version $Revision: 1.192 $ $Date: 2012/04/16 19:40:39 $
* @author Ernst de Haan
*
* @since XINS 1.0.0
*/
public class XINSServiceCaller extends ServiceCaller {
/**
* The result parser. This field cannot be null
.
*/
private final XINSCallResultParser _parser;
/**
* The CAPI
object that uses this caller. This field is
* null
if this caller is not used by a CAPI
* class.
*/
private AbstractCAPI _capi;
/**
* The map containing the service caller to call for the descriptor.
* The key of the {@link HashMap} is a {@link TargetDescriptor} and the value
* is a {@link ServiceCaller}.
*/
private HashMap _serviceCallers;
/**
* Constructs a new XINSServiceCaller
with the specified
* descriptor and call configuration.
*
* @param descriptor
* the descriptor of the service, cannot be null
.
*
* @param callConfig
* the call configuration object for this service caller, or
* null
if a default one should be associated with this
* service caller.
*
* @throws IllegalArgumentException
* if descriptor == null
.
*
* @throws UnsupportedProtocolException
* if descriptor
is or contains a {@link TargetDescriptor}
* with an unsupported protocol.
*
* @since XINS 1.1.0
*/
public XINSServiceCaller(Descriptor descriptor, XINSCallConfig callConfig)
throws IllegalArgumentException, UnsupportedProtocolException {
// Call constructor of superclass
super(descriptor, callConfig);
// Initialize the fields
_parser = new XINSCallResultParser();
}
/**
* Constructs a new XINSServiceCaller
with the specified
* descriptor and the default HTTP method.
*
* @param descriptor
* the descriptor of the service, cannot be null
.
*
* @throws IllegalArgumentException
* if descriptor == null
.
*
* @throws UnsupportedProtocolException
* if descriptor
is or contains a {@link TargetDescriptor}
* with an unsupported protocol (since XINS 1.1.0).
*/
public XINSServiceCaller(Descriptor descriptor)
throws IllegalArgumentException, UnsupportedProtocolException {
this(descriptor, null);
}
/**
* Constructs a new XINSServiceCaller
with no
* descriptor (yet) and the default HTTP method.
*
* Before actual calls can be made, {@link #setDescriptor(Descriptor)}
* should be used to set the descriptor.
*
* @since XINS 1.2.0
*/
public XINSServiceCaller() {
this((Descriptor) null, (XINSCallConfig) null);
}
/**
* Checks if the specified protocol is supported (implementation method).
* The protocol is the part in a URL before the string "://"
).
*
*
This method should only ever be called from the
* {@link #isProtocolSupported(String)} method.
*
*
The implementation of this method in class ServiceCaller
* throws an {@link UnsupportedOperationException}.
*
* @param protocol
* the protocol, guaranteed not to be null
.
*
* @return
* true
if the specified protocol is supported, or
* false
if it is not.
*
* @since XINS 1.2.0
*/
protected boolean isProtocolSupportedImpl(String protocol) {
return "http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol) || "file".equalsIgnoreCase(protocol);
}
public void setDescriptor(Descriptor descriptor) {
super.setDescriptor(descriptor);
// Create the ServiceCaller for each descriptor
if (_serviceCallers == null) {
_serviceCallers = new HashMap();
}
if (descriptor != null) {
for (TargetDescriptor nextTarget : descriptor) {
String protocol = nextTarget.getProtocol();
if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol)) {
HTTPServiceCaller serviceCaller = new HTTPServiceCaller(nextTarget);
_serviceCallers.put(nextTarget, serviceCaller);
} else if ("file".equalsIgnoreCase(protocol)) {
FileServiceCaller serviceCaller = new FileServiceCaller(nextTarget);
_serviceCallers.put(nextTarget, serviceCaller);
}
}
} else {
_serviceCallers.clear();
}
}
/**
* Sets the associated CAPI
instance.
*
*
This method is expected to be called only once, before any calls are
* made with this caller.
*
* @param capi
* the associated CAPI
instance, or
* null
.
*/
void setCAPI(AbstractCAPI capi) {
_capi = capi;
}
/**
* Returns a default CallConfig
object. This method is called
* by the ServiceCaller
constructor if no
* CallConfig
object was given.
*
*
The implementation of this method in class {@link XINSServiceCaller}
* returns a standard {@link XINSCallConfig} object which has unconditional
* fail-over disabled and the HTTP method set to
* {@link org.xins.common.http.HTTPMethod#POST POST}.
*
* @return
* a new {@link XINSCallConfig} instance with default settings, never
* null
.
*/
@Override
protected CallConfig getDefaultCallConfig() {
return new XINSCallConfig();
}
/**
* Sets the XINSCallConfig
associated with this XINS service
* caller.
*
* @param config
* the fall-back {@link XINSCallConfig} object for this service caller,
* cannot be null
.
*
* @throws IllegalArgumentException
* if config == null
.
*
* @since XINS 1.2.0
*/
protected final void setXINSCallConfig(XINSCallConfig config)
throws IllegalArgumentException {
super.setCallConfig(config);
}
/**
* Returns the XINSCallConfig
associated with this service
* caller.
*
*
This method is the type-safe equivalent of {@link #getCallConfig()}.
*
* @return
* the fall-back {@link XINSCallConfig} object for this XINS service
* caller, never null
.
*
* @since XINS 1.2.0
*/
public final XINSCallConfig getXINSCallConfig() {
return (XINSCallConfig) getCallConfig();
}
/**
* Executes the specified XINS call request towards one of the associated
* targets. If the call succeeds with one of these targets, then a
* {@link XINSCallResult} object is returned. Otherwise, if none of the
* targets could successfully be called, a
* {@link org.xins.common.service.CallException} is thrown.
*
*
If the call succeeds, but the result is unsuccessful, then an
* {@link UnsuccessfulXINSCallException} is thrown, which contains the
* result.
*
* @param request
* the call request, not null
.
*
* @param callConfig
* the call configuration, or null
if the one specified in
* the request should be used, or -if the request does not specify any
* either- the one specified for this service caller.
*
* @return
* the result of the call, cannot be null
.
*
* @throws IllegalArgumentException
* if request == null
.
*
* @throws GenericCallException
* if the first call attempt failed due to a generic reason and all the
* other call attempts failed as well.
*
* @throws HTTPCallException
* if the first call attempt failed due to an HTTP-related reason and
* all the other call attempts failed as well.
*
* @throws XINSCallException
* if the first call attempt failed due to a XINS-related reason and
* all the other call attempts failed as well.
*
* @since XINS 1.1.0
*/
public XINSCallResult call(XINSCallRequest request,
XINSCallConfig callConfig)
throws IllegalArgumentException,
GenericCallException,
HTTPCallException,
XINSCallException {
// Determine when we started the call
long start = System.currentTimeMillis();
// Perform the call
XINSCallResult result;
try {
result = (XINSCallResult) doCall(request,callConfig);
// Handle failures
} catch (Throwable exception) {
// Log that the call completely failed, unless the back-end returned
// a functional error code. We assume that a functional error code
// can never fail-over, so this issue will have been logged at the
// correct (non-error) level already.
if (!(exception instanceof UnsuccessfulXINSCallException) ||
((UnsuccessfulXINSCallException) exception).getType() != ErrorCodeSpec.FUNCTIONAL) {
// Determine how long the call took
long duration = System.currentTimeMillis() - start;
// Serialize all parameters, including the data section, for logging
Map parameters = request.getParameters();
Element dataSection = request.getDataSection();
FormattedParameters params = new FormattedParameters(parameters, dataSection, "(null)", "&", 160);
// Serialize the exception chain
String chain = exception.getMessage();
Log.log_2113(request.getFunctionName(), params, duration, chain);
}
// Allow only GenericCallException, HTTPCallException and
// XINSCallException to proceed
if (exception instanceof GenericCallException) {
throw (GenericCallException) exception;
} if (exception instanceof HTTPCallException) {
throw (HTTPCallException) exception;
} if (exception instanceof XINSCallException) {
throw (XINSCallException) exception;
// Unknown kind of exception. This should never happen. Log and
// re-throw the exception, wrapped within a ProgrammingException
} else {
throw Utils.logProgrammingError(exception);
}
}
return result;
}
/**
* Executes the specified XINS call request towards one of the associated
* targets. If the call succeeds with one of these targets, then a
* {@link XINSCallResult} object is returned. Otherwise, if none of the
* targets could successfully be called, a
* {@link org.xins.common.service.CallException} is thrown.
*
* If the call succeeds, but the result is unsuccessful, then an
* {@link UnsuccessfulXINSCallException} is thrown, which contains the
* result.
*
* @param request
* the call request, not null
.
*
* @return
* the result of the call, cannot be null
.
*
* @throws IllegalArgumentException
* if request == null
.
*
* @throws GenericCallException
* if the first call attempt failed due to a generic reason and all the
* other call attempts failed as well.
*
* @throws HTTPCallException
* if the first call attempt failed due to an HTTP-related reason and
* all the other call attempts failed as well.
*
* @throws XINSCallException
* if the first call attempt failed due to a XINS-related reason and
* all the other call attempts failed as well.
*/
public XINSCallResult call(XINSCallRequest request)
throws IllegalArgumentException,
GenericCallException,
HTTPCallException,
XINSCallException {
return call(request, null);
}
/**
* Executes the specified request on the given target. If the call
* succeeds, then a {@link XINSCallResult} object is returned, otherwise a
* {@link org.xins.common.service.CallException} is thrown.
*
* @param target
* the target to call, cannot be null
.
*
* @param callConfig
* the call configuration, never null
.
*
* @param request
* the call request to be executed, must be an instance of class
* {@link XINSCallRequest}, cannot be null
.
*
* @return
* the result, if and only if the call succeeded, always an instance of
* class {@link XINSCallResult}, never null
.
*
* @throws IllegalArgumentException
* if request == null
* || callConfig == null
* || target == null
.
*
* @throws ClassCastException
* if the specified request
object is not null
* and not an instance of class {@link XINSCallRequest}.
*
* @throws GenericCallException
* if the call attempt failed due to a generic reason.
* other call attempts failed as well.
*
* @throws HTTPCallException
* if the call attempt failed due to an HTTP-related reason.
*
* @throws XINSCallException
* if the call attempt failed due to a XINS-related reason.
*/
public Object doCallImpl(CallRequest request,
CallConfig callConfig,
TargetDescriptor target)
throws IllegalArgumentException,
ClassCastException,
GenericCallException,
HTTPCallException,
XINSCallException {
// Check preconditions
MandatoryArgumentChecker.check("request", request,
"callConfig", callConfig,
"target", target);
// Convert arguments to the appropriate classes
XINSCallRequest xinsRequest = (XINSCallRequest) request;
XINSCallConfig xinsConfig = (XINSCallConfig) callConfig;
// Get URL, function and parameters (for logging)
String url = target.getURL();
String function = xinsRequest.getFunctionName();
Map p = xinsRequest.getParameters();
Element dataSection = xinsRequest.getDataSection();
FormattedParameters params = new FormattedParameters(p, dataSection, "", "&", 160);
// Get the time-out values (for logging)
int totalTimeOut = target.getTotalTimeOut();
int connectionTimeOut = target.getConnectionTimeOut();
int socketTimeOut = target.getSocketTimeOut();
// Log: Right before the call is performed
Log.log_2100(url, function, params);
// Get the contained HTTP request from the XINS request
HTTPCallRequest httpRequest = xinsRequest.getHTTPCallRequest();
// Convert XINSCallConfig to HTTPCallConfig
HTTPCallConfig httpConfig = xinsConfig.getHTTPCallConfig();
// Determine the start time. Only required when an unexpected kind of
// exception is caught.
long start = System.currentTimeMillis();
// Perform the HTTP call
HTTPCallResult httpResult;
long duration;
try {
ServiceCaller serviceCaller = (ServiceCaller) _serviceCallers.get(target);
httpResult = (HTTPCallResult) serviceCaller.doCallImpl(httpRequest, httpConfig, target);
// Call failed due to a generic service calling error
} catch (GenericCallException exception) {
duration = exception.getDuration();
if (exception instanceof UnknownHostCallException) {
Log.log_2102(url, function, params, duration);
} else if (exception instanceof ConnectionRefusedCallException) {
Log.log_2103(url, function, params, duration);
} else if (exception instanceof ConnectionTimeOutCallException) {
Log.log_2104(url, function, params, duration, connectionTimeOut);
} else if (exception instanceof SocketTimeOutCallException) {
Log.log_2105(url, function, params, duration, socketTimeOut);
} else if (exception instanceof TotalTimeOutCallException) {
Log.log_2106(url, function, params, duration, totalTimeOut);
} else if (exception instanceof IOCallException) {
Log.log_2109(exception, url, function, params, duration);
} else if (exception instanceof UnexpectedExceptionCallException) {
Log.log_2111(ExceptionUtils.getRootCause(exception), url, function, params, duration);
} else {
String detail = "Unrecognized GenericCallException subclass "
+ exception.getClass().getName() + '.';
Utils.logProgrammingError(detail);
}
logTransaction(exception, start, url, function, duration, null, params, null);
throw exception;
// Call failed due to an HTTP-related error
} catch (HTTPCallException exception) {
duration = exception.getDuration();
if (exception instanceof StatusCodeHTTPCallException) {
int code = ((StatusCodeHTTPCallException) exception).getStatusCode();
Log.log_2108(url, function, params, duration, code);
} else {
String detail = "Unrecognized HTTPCallException subclass "
+ exception.getClass().getName() + '.';
Utils.logProgrammingError(detail);
}
logTransaction(exception, start, url, function, duration, null, params, null);
throw exception;
// Unknown kind of exception. This should never happen. Log and re-throw
// the exception, packed up as a CallException.
} catch (Throwable exception) {
duration = System.currentTimeMillis() - start;
Utils.logProgrammingError(exception);
String message = "Unexpected exception: " + exception.getClass().getName()
+ ". Message: " + TextUtils.quote(exception.getMessage()) + '.';
Log.log_2111(exception, url, function, params, duration);
logTransaction(exception, start, url, function, duration, null, params, null);
throw new UnexpectedExceptionCallException(request, target, duration, message, exception);
}
// Determine duration
duration = httpResult.getDuration();
// Make sure data was received
byte[] httpData = httpResult.getData();
if (httpData == null || httpData.length == 0) {
// Log: No data was received
Log.log_2110(url, function, params, duration, "No data received.");
logTransaction(null, start, url, function, duration, "=NoHTTPData", params, null);
// Throw an appropriate exception
throw InvalidResultXINSCallException.noDataReceived(xinsRequest, target, duration);
}
// Parse the result
XINSCallResultData resultData;
try {
resultData = _parser.parse(httpData);
// If parsing failed, then abort
} catch (ParseException exception) {
// Create a message for the new exception
String detail = exception.getDetail();
String message = detail != null && detail.trim().length() > 0
? "Failed to parse result: " + detail.trim()
: "Failed to parse result.";
// Log: Parsing failed
Log.log_2110(url, function, params, duration, message);
logTransaction(exception, start, url, function, duration, "=ParseError", params, null);
// Throw an appropriate exception
throw InvalidResultXINSCallException.parseError(httpData, xinsRequest, target, duration, exception);
}
// Convert the output parameters to a FormattedParameters object
FormattedParameters outParams = new FormattedParameters(resultData.getParameters(), resultData.getDataElement(), "(null)", "&", 160);
// If the result is unsuccessful, then throw an exception
String errorCode = resultData.getErrorCode();
if (errorCode != null) {
boolean functionalError = false;
ErrorCodeSpec.Type type = null;
if (_capi != null) {
functionalError = _capi.isFunctionalError(errorCode);
}
// Log this
if (functionalError) {
Log.log_2115(url, function, params, duration, errorCode);
} else {
Log.log_2112(url, function, params, duration, errorCode);
}
logTransaction(null, start, url, function, duration, errorCode, params, outParams);
// Standard error codes (start with an underscore)
if (errorCode.charAt(0) == '_') {
if (errorCode.equals("_DisabledFunction")) {
throw new DisabledFunctionException(xinsRequest, target, duration, resultData);
} else if (errorCode.equals("_InternalError") || errorCode.equals("_InvalidResponse")) {
throw new InternalErrorException(xinsRequest, target, duration, resultData);
} else if (errorCode.equals("_InvalidRequest")) {
throw new InvalidRequestException(xinsRequest, target, duration, resultData);
} else {
throw new UnacceptableErrorCodeXINSCallException(xinsRequest, target, duration, resultData);
}
// Non-standard error codes, CAPI not used
} else if (_capi == null) {
throw new UnsuccessfulXINSCallException(xinsRequest, target, duration, resultData, null);
// Non-standard error codes, CAPI used
} else {
AbstractCAPIErrorCodeException ex = _capi.createErrorCodeException(xinsRequest, target, duration, resultData);
if (ex != null) {
ex.setType(type);
throw ex;
} else {
// If the CAPI class was generated using a XINS release older
// than 1.2.0, then it will not override the
// 'createErrorCodeException' method and consequently the
// method will return null. It cannot be determined here
// whether the error code is acceptable or not
String ver = _capi.getXINSVersion();
if (ver.startsWith("0.")
|| ver.startsWith("1.0.")
|| ver.startsWith("1.1.")) {
throw new UnsuccessfulXINSCallException(
xinsRequest, target, duration, resultData, null);
} else {
throw new UnacceptableErrorCodeXINSCallException(
xinsRequest, target, duration, resultData);
}
}
}
}
// Call completely succeeded
Log.log_2101(url, function, params, duration);
logTransaction(null, start, url, function, duration, errorCode, params, outParams);
return resultData;
}
/**
* Constructs an appropriate CallResult
object for a
* successful call attempt. This method is called from
* {@link #doCall(CallRequest,CallConfig)}.
*
* The implementation of this method in class
* {@link XINSServiceCaller} expects an {@link XINSCallRequest} and
* returns an {@link XINSCallResult}.
*
* @param request
* the {@link CallRequest} that was to be executed, never
* null
when called from {@link #doCall(CallRequest,CallConfig)};
* should be an instance of class {@link XINSCallRequest}.
*
* @param succeededTarget
* the {@link TargetDescriptor} for the service that was successfully
* called, never null
when called from
* {@link #doCall(CallRequest,CallConfig)}.
*
* @param duration
* the call duration in milliseconds, must be a non-negative number.
*
* @param exceptions
* the list of {@link org.xins.common.service.CallException} instances,
* or null
if there were no call failures.
*
* @param result
* the result from the call, which is the object returned by
* {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)}, always an instance
* of class {@link XINSCallResult}, never null
; .
*
* @return
* a {@link XINSCallResult} instance, never null
.
*
* @throws ClassCastException
* if either request
or result
is not of the
* correct class.
*/
protected CallResult createCallResult(CallRequest request,
TargetDescriptor succeededTarget,
long duration,
List exceptions,
Object result)
throws ClassCastException {
XINSCallResult r = new XINSCallResult((XINSCallRequest) request,
succeededTarget,
duration,
exceptions,
(XINSCallResultData) result);
return r;
}
/**
* Determines whether a call should fail-over to the next selected target
* based on a request, call configuration and exception list.
*
* @param request
* the request for the call, as passed to {@link #doCall(CallRequest,CallConfig)},
* should not be null
.
*
* @param callConfig
* the call config that is currently in use, never null
.
*
* @param exceptions
* the current list of {@link CallException}s; never null
and never empty.
*
* @return
* true
if the call should fail-over to the next target, or
* false
if it should not.
*/
protected boolean shouldFailOver(CallRequest request,
CallConfig callConfig,
List exceptions) {
// Get the most recent exception
CallException exception = exceptions.get(exceptions.size() - 1);
boolean should;
// Let the superclass look at this first.
if (super.shouldFailOver(request, callConfig, exceptions)) {
should = true;
// Otherwise check if the request may fail-over from HTTP point-of-view
//
// XXX: Note that this duplicates code that is already in the
// HTTPServiceCaller. This may need to be refactored at some point.
// It has been decided to take this approach since the
// shouldFailOver method in class HTTPServiceCaller has protected
// access.
// An alternative solution that should be investigated is to
// subclass HTTPServiceCaller.
// A non-2xx HTTP status code indicates the request was not handled
} else if (exception instanceof StatusCodeHTTPCallException) {
int code = ((StatusCodeHTTPCallException) exception).getStatusCode();
should = (code < 200 || code > 299);
// Some XINS error codes indicate the request was not accepted
} else if (exception instanceof UnsuccessfulXINSCallException) {
String s = ((UnsuccessfulXINSCallException) exception).getErrorCode();
should = ("_InvalidRequest".equals(s)
|| "_DisabledFunction".equals(s));
// Otherwise do not fail over
} else {
should = false;
}
return should;
}
/**
* Performs (client-side) transaction logging.
*
* @param exception
* the exception, if any, can be null
.
*
* @param start
* the start as the number of milliseconds since the UNIX Epoch.
*
* @param url
* the URL that is being called, should not be null
.
*
* @param functionName
* the name of the function that is being invoked, should not be null
.
*
* @param duration
* the duration of the call in milliseconds.
*
* @param errorCode
* the error code, should not be null
.
*
* @param inParams
* the input parameters, should not be null
.
*
* @param outParams
* the output parameters, should not be null
.
*/
private static void logTransaction(Throwable exception, long start, String url, String functionName, long duration, String errorCode, FormattedParameters inParams, FormattedParameters outParams) {
final Object hyphen = "-";
if (errorCode == null && exception != null) {
String exceptionName = exception.getClass().getName();
exceptionName = exceptionName.substring(exceptionName.lastIndexOf('.') + 1);
if (exceptionName.endsWith("CallException")) {
exceptionName = exceptionName.substring(0, exceptionName.length() - 13);
}
errorCode = "=" + exceptionName;
}
errorCode = (errorCode == null) ? "0" : errorCode;
Object inParamsObject = (inParams == null) ? hyphen : inParams;
Object outParamsObject = (outParams == null) ? hyphen : outParams;
Log.log_2300(exception, start, url, functionName, duration, errorCode, inParamsObject, outParamsObject);
Log.log_2301(exception, start, url, functionName, duration, errorCode);
}
}