001: /*
002: * $Id: XINSServiceCaller.java,v 1.180 2007/09/11 10:14:20 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.client;
008:
009: import java.util.HashMap;
010: import java.util.Iterator;
011: import org.xins.common.FormattedParameters;
012:
013: import org.xins.logdoc.ExceptionUtils;
014:
015: import org.xins.common.MandatoryArgumentChecker;
016: import org.xins.common.Utils;
017: import org.xins.common.collections.PropertyReader;
018: import org.xins.common.http.HTTPCallConfig;
019: import org.xins.common.http.HTTPCallException;
020: import org.xins.common.http.HTTPCallRequest;
021: import org.xins.common.http.HTTPCallResult;
022: import org.xins.common.http.HTTPServiceCaller;
023: import org.xins.common.http.StatusCodeHTTPCallException;
024: import org.xins.common.service.CallConfig;
025: import org.xins.common.service.CallException;
026: import org.xins.common.service.CallExceptionList;
027: import org.xins.common.service.CallRequest;
028: import org.xins.common.service.CallResult;
029: import org.xins.common.service.ConnectionTimeOutCallException;
030: import org.xins.common.service.ConnectionRefusedCallException;
031: import org.xins.common.service.Descriptor;
032: import org.xins.common.service.GenericCallException;
033: import org.xins.common.service.IOCallException;
034: import org.xins.common.service.ServiceCaller;
035: import org.xins.common.service.SocketTimeOutCallException;
036: import org.xins.common.service.TargetDescriptor;
037: import org.xins.common.service.TotalTimeOutCallException;
038: import org.xins.common.service.UnexpectedExceptionCallException;
039: import org.xins.common.service.UnknownHostCallException;
040: import org.xins.common.service.UnsupportedProtocolException;
041: import org.xins.common.spec.ErrorCodeSpec;
042: import org.xins.common.text.ParseException;
043: import org.xins.common.text.TextUtils;
044: import org.xins.common.xml.Element;
045:
046: /**
047: * XINS service caller. This class can be used to perform a call to a XINS
048: * service, over HTTP, and fail-over to other XINS services if the first one
049: * fails.
050: *
051: * <h2>Supported protocols</h2>
052: *
053: * <p>This service caller currently only supports the HTTP protocol. If a
054: * {@link TargetDescriptor} is passed to the constructor with a different
055: * protocol, then an {@link UnsupportedProtocolException} is thrown. In the
056: * future, HTTPS and other protocols are expected to be supported as well.
057: *
058: * <h2>Load-balancing and fail-over</h2>
059: *
060: * <p>To perform a XINS call, use {@link #call(XINSCallRequest)}. Fail-over
061: * and load-balancing can be performed automatically.
062: *
063: * <p>How load-balancing is done depends on the {@link Descriptor} passed to
064: * the {@link #XINSServiceCaller(Descriptor)} constructor. If it is a
065: * {@link TargetDescriptor}, then only this single target service is called
066: * and no load-balancing is performed. If it is a
067: * {@link org.xins.common.service.GroupDescriptor}, then the configuration of
068: * the <code>GroupDescriptor</code> determines how the load-balancing is done.
069: * A <code>GroupDescriptor</code> is a recursive data structure, which allows
070: * for fairly advanced load-balancing algorithms.
071: *
072: * <p>If a call attempt fails and there are more available target services,
073: * then the <code>XINSServiceCaller</code> may or may not fail-over to a next
074: * target. If the request was not accepted by the target service, then
075: * fail-over is considered acceptable and will be performed. This includes
076: * the following situations:
077: *
078: * <ul>
079: * <li>if the <em>failOverAllowed</em> property is set to <code>true</code>
080: * for the {@link XINSCallRequest};
081: * <li>on connection refusal;
082: * <li>if a connection attempt times out;
083: * <li>if an HTTP status code other than 200-299 is returned;
084: * <li>if the XINS error code <em>_InvalidRequest</em> is returned;
085: * <li>if the XINS error code <em>_DisabledFunction</em> is returned.
086: * </ul>
087: *
088: * <p>If none of these conditions holds, then fail-over is not considered
089: * acceptable and will not be performed.
090: *
091: * <h2>Example code</h2>
092: *
093: * <p>The following example code snippet constructs a
094: * <code>XINSServiceCaller</code> instance:
095: *
096: * <blockquote><pre>// Initialize properties for the services. Normally these
097: // properties would come from a configuration source, like a file.
098: {@link org.xins.common.collections.BasicPropertyReader} properties = new {@link org.xins.common.collections.BasicPropertyReader#BasicPropertyReader() org.xins.common.collections.BasicPropertyReader}();
099: properties.{@link org.xins.common.collections.BasicPropertyReader#set(String,String) set}("myapi", "group, random, server1, server2");
100: properties.{@link org.xins.common.collections.BasicPropertyReader#set(String,String) set}("myapi.server1", "service, http://server1/myapi, 10000");
101: properties.{@link org.xins.common.collections.BasicPropertyReader#set(String,String) set}("myapi.server2", "service, http://server2/myapi, 12000");
102:
103: // Construct a descriptor and a XINSServiceCaller instance
104: {@link Descriptor Descriptor} descriptor = {@link org.xins.common.service.DescriptorBuilder DescriptorBuilder}.{@link org.xins.common.service.DescriptorBuilder#build(PropertyReader,String) build}(properties, "myapi");
105: XINSServiceCaller caller = new {@link #XINSServiceCaller(Descriptor) XINSServiceCaller}(descriptor);</pre></blockquote>
106: *
107: * <p>Then the following code snippet uses this <code>XINSServiceCaller</code>
108: * to perform a call to a XINS function named <em>_GetStatistics</em>, using
109: * HTTP POST:
110: *
111: * <blockquote><pre>// Prepare for the call
112: {@link String} function = "_GetStatistics";
113: {@link org.xins.common.collections.PropertyReader} params = null;
114: boolean failOver = true;
115: {@link org.xins.common.http.HTTPMethod} method = {@link org.xins.common.http.HTTPMethod}.{@link org.xins.common.http.HTTPMethod#POST POST};
116: {@link XINSCallRequest} request = new {@link XINSCallRequest#XINSCallRequest(String,PropertyReader,boolean,HTTPMethod) XINSCallRequest}(function, params, failOver, method);
117:
118: // Perform the call
119: {@link XINSCallResult} result = caller.{@link #call(XINSCallRequest) call}(request);</pre></blockquote>
120: *
121: * @version $Revision: 1.180 $ $Date: 2007/09/11 10:14:20 $
122: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
123: *
124: * @since XINS 1.0.0
125: */
126: public final class XINSServiceCaller extends ServiceCaller {
127:
128: /**
129: * The result parser. This field cannot be <code>null</code>.
130: */
131: private final XINSCallResultParser _parser;
132:
133: /**
134: * The <code>CAPI</code> object that uses this caller. This field is
135: * <code>null</code> if this caller is not used by a <code>CAPI</code>
136: * class.
137: */
138: private AbstractCAPI _capi;
139:
140: /**
141: * The map containing the service caller to call for the descriptor.
142: * The key of the {@link HashMap} is a {@link TargetDescriptor} and the value
143: * is a {@link ServiceCaller}.
144: */
145: private HashMap _serviceCallers;
146:
147: /**
148: * Constructs a new <code>XINSServiceCaller</code> with the specified
149: * descriptor and call configuration.
150: *
151: * @param descriptor
152: * the descriptor of the service, cannot be <code>null</code>.
153: *
154: * @param callConfig
155: * the call configuration object for this service caller, or
156: * <code>null</code> if a default one should be associated with this
157: * service caller.
158: *
159: * @throws IllegalArgumentException
160: * if <code>descriptor == null</code>.
161: *
162: * @throws UnsupportedProtocolException
163: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
164: * with an unsupported protocol.
165: *
166: * @since XINS 1.1.0
167: */
168: public XINSServiceCaller(Descriptor descriptor,
169: XINSCallConfig callConfig) throws IllegalArgumentException,
170: UnsupportedProtocolException {
171:
172: // Call constructor of superclass
173: super (descriptor, callConfig);
174:
175: // Initialize the fields
176: _parser = new XINSCallResultParser();
177: }
178:
179: /**
180: * Constructs a new <code>XINSServiceCaller</code> with the specified
181: * descriptor and the default HTTP method.
182: *
183: * @param descriptor
184: * the descriptor of the service, cannot be <code>null</code>.
185: *
186: * @throws IllegalArgumentException
187: * if <code>descriptor == null</code>.
188: *
189: * @throws UnsupportedProtocolException
190: * if <code>descriptor</code> is or contains a {@link TargetDescriptor}
191: * with an unsupported protocol (<em>since XINS 1.1.0</em>).
192: */
193: public XINSServiceCaller(Descriptor descriptor)
194: throws IllegalArgumentException,
195: UnsupportedProtocolException {
196: this (descriptor, null);
197: }
198:
199: /**
200: * Constructs a new <code>XINSServiceCaller</code> with the specified
201: * descriptor and the default HTTP method.
202: *
203: * @since XINS 1.2.0
204: */
205: public XINSServiceCaller() {
206: this ((Descriptor) null, (XINSCallConfig) null);
207: }
208:
209: /**
210: * Checks if the specified protocol is supported (implementation method).
211: * The protocol is the part in a URL before the string <code>"://"</code>).
212: *
213: * <p>This method should only ever be called from the
214: * {@link #isProtocolSupported(String)} method.
215: *
216: * <p>The implementation of this method in class <code>ServiceCaller</code>
217: * throws an {@link UnsupportedOperationException}.
218: *
219: * @param protocol
220: * the protocol, guaranteed not to be <code>null</code>.
221: *
222: * @return
223: * <code>true</code> if the specified protocol is supported, or
224: * <code>false</code> if it is not.
225: *
226: * @since XINS 1.2.0
227: */
228: protected boolean isProtocolSupportedImpl(String protocol) {
229: return "http".equalsIgnoreCase(protocol)
230: || "https".equalsIgnoreCase(protocol)
231: || "file".equalsIgnoreCase(protocol);
232: }
233:
234: public void setDescriptor(Descriptor descriptor) {
235: super .setDescriptor(descriptor);
236:
237: // Create the ServiceCaller for each descriptor
238: if (_serviceCallers == null) {
239: _serviceCallers = new HashMap();
240: }
241: if (descriptor != null) {
242: Iterator targets = descriptor.iterateTargets();
243: while (targets.hasNext()) {
244: TargetDescriptor nextTarget = (TargetDescriptor) targets
245: .next();
246: String protocol = nextTarget.getProtocol();
247: if ("http".equalsIgnoreCase(protocol)
248: || "https".equalsIgnoreCase(protocol)) {
249: HTTPServiceCaller serviceCaller = new HTTPServiceCaller(
250: nextTarget);
251: _serviceCallers.put(nextTarget, serviceCaller);
252: } else if ("file".equalsIgnoreCase(protocol)) {
253: FileServiceCaller serviceCaller = new FileServiceCaller(
254: nextTarget);
255: _serviceCallers.put(nextTarget, serviceCaller);
256: }
257: }
258: } else {
259: _serviceCallers.clear();
260: }
261: }
262:
263: /**
264: * Sets the associated <code>CAPI</code> instance.
265: *
266: * <p>This method is expected to be called only once, before any calls are
267: * made with this caller.
268: *
269: * @param capi
270: * the associated <code>CAPI</code> instance, or
271: * <code>null</code>.
272: */
273: void setCAPI(AbstractCAPI capi) {
274: _capi = capi;
275: }
276:
277: /**
278: * Returns a default <code>CallConfig</code> object. This method is called
279: * by the <code>ServiceCaller</code> constructor if no
280: * <code>CallConfig</code> object was given.
281: *
282: * <p>The implementation of this method in class {@link XINSServiceCaller}
283: * returns a standard {@link XINSCallConfig} object which has unconditional
284: * fail-over disabled and the HTTP method set to
285: * {@link org.xins.common.http.HTTPMethod#POST POST}.
286: *
287: * @return
288: * a new {@link XINSCallConfig} instance with default settings, never
289: * <code>null</code>.
290: */
291: protected CallConfig getDefaultCallConfig() {
292: return new XINSCallConfig();
293: }
294:
295: /**
296: * Sets the <code>XINSCallConfig</code> associated with this XINS service
297: * caller.
298: *
299: * @param config
300: * the fall-back {@link XINSCallConfig} object for this service caller,
301: * cannot be <code>null</code>.
302: *
303: * @throws IllegalArgumentException
304: * if <code>config == null</code>.
305: *
306: * @since XINS 1.2.0
307: */
308: protected final void setXINSCallConfig(XINSCallConfig config)
309: throws IllegalArgumentException {
310: super .setCallConfig(config);
311: }
312:
313: /**
314: * Returns the <code>XINSCallConfig</code> associated with this service
315: * caller.
316: *
317: * <p>This method is the type-safe equivalent of {@link #getCallConfig()}.
318: *
319: * @return
320: * the fall-back {@link XINSCallConfig} object for this XINS service
321: * caller, never <code>null</code>.
322: *
323: * @since XINS 1.2.0
324: */
325: public final XINSCallConfig getXINSCallConfig() {
326: return (XINSCallConfig) getCallConfig();
327: }
328:
329: /**
330: * Executes the specified XINS call request towards one of the associated
331: * targets. If the call succeeds with one of these targets, then a
332: * {@link XINSCallResult} object is returned. Otherwise, if none of the
333: * targets could successfully be called, a
334: * {@link org.xins.common.service.CallException} is thrown.
335: *
336: * <p>If the call succeeds, but the result is unsuccessful, then an
337: * {@link UnsuccessfulXINSCallException} is thrown, which contains the
338: * result.
339: *
340: * @param request
341: * the call request, not <code>null</code>.
342: *
343: * @param callConfig
344: * the call configuration, or <code>null</code> if the one specified in
345: * the request should be used, or -if the request does not specify any
346: * either- the one specified for this service caller.
347: *
348: * @return
349: * the result of the call, cannot be <code>null</code>.
350: *
351: * @throws IllegalArgumentException
352: * if <code>request == null</code>.
353: *
354: * @throws GenericCallException
355: * if the first call attempt failed due to a generic reason and all the
356: * other call attempts failed as well.
357: *
358: * @throws HTTPCallException
359: * if the first call attempt failed due to an HTTP-related reason and
360: * all the other call attempts failed as well.
361: *
362: * @throws XINSCallException
363: * if the first call attempt failed due to a XINS-related reason and
364: * all the other call attempts failed as well.
365: *
366: * @since XINS 1.1.0
367: */
368: public XINSCallResult call(XINSCallRequest request,
369: XINSCallConfig callConfig) throws IllegalArgumentException,
370: GenericCallException, HTTPCallException, XINSCallException {
371:
372: // Determine when we started the call
373: long start = System.currentTimeMillis();
374:
375: // Perform the call
376: XINSCallResult result;
377: try {
378: result = (XINSCallResult) doCall(request, callConfig);
379:
380: // Handle failures
381: } catch (Throwable exception) {
382:
383: // Log that the call completely failed, unless the back-end returned
384: // a functional error code. We assume that a functional error code
385: // can never fail-over, so this issue will have been logged at the
386: // correct (non-error) level already.
387: if (!(exception instanceof UnsuccessfulXINSCallException)
388: || ((UnsuccessfulXINSCallException) exception)
389: .getType() != ErrorCodeSpec.FUNCTIONAL) {
390:
391: // Determine how long the call took
392: long duration = System.currentTimeMillis() - start;
393:
394: // Serialize all parameters, including the data section, for logging
395: PropertyReader parameters = request.getParameters();
396: Element dataSection = request.getDataSection();
397: FormattedParameters params = new FormattedParameters(
398: parameters, dataSection, "(null)", "&", 160);
399:
400: // Serialize the exception chain
401: String chain = exception.getMessage();
402:
403: Log.log_2113(request.getFunctionName(), params,
404: duration, chain);
405: }
406:
407: // Allow only GenericCallException, HTTPCallException and
408: // XINSCallException to proceed
409: if (exception instanceof GenericCallException) {
410: throw (GenericCallException) exception;
411: }
412: if (exception instanceof HTTPCallException) {
413: throw (HTTPCallException) exception;
414: }
415: if (exception instanceof XINSCallException) {
416: throw (XINSCallException) exception;
417:
418: // Unknown kind of exception. This should never happen. Log and
419: // re-throw the exception, wrapped within a ProgrammingException
420: } else {
421: throw Utils.logProgrammingError(exception);
422: }
423: }
424:
425: return result;
426: }
427:
428: /**
429: * Executes the specified XINS call request towards one of the associated
430: * targets. If the call succeeds with one of these targets, then a
431: * {@link XINSCallResult} object is returned. Otherwise, if none of the
432: * targets could successfully be called, a
433: * {@link org.xins.common.service.CallException} is thrown.
434: *
435: * <p>If the call succeeds, but the result is unsuccessful, then an
436: * {@link UnsuccessfulXINSCallException} is thrown, which contains the
437: * result.
438: *
439: * @param request
440: * the call request, not <code>null</code>.
441: *
442: * @return
443: * the result of the call, cannot be <code>null</code>.
444: *
445: * @throws IllegalArgumentException
446: * if <code>request == null</code>.
447: *
448: * @throws GenericCallException
449: * if the first call attempt failed due to a generic reason and all the
450: * other call attempts failed as well.
451: *
452: * @throws HTTPCallException
453: * if the first call attempt failed due to an HTTP-related reason and
454: * all the other call attempts failed as well.
455: *
456: * @throws XINSCallException
457: * if the first call attempt failed due to a XINS-related reason and
458: * all the other call attempts failed as well.
459: */
460: public XINSCallResult call(XINSCallRequest request)
461: throws IllegalArgumentException, GenericCallException,
462: HTTPCallException, XINSCallException {
463: return call(request, null);
464: }
465:
466: /**
467: * Executes the specified request on the given target. If the call
468: * succeeds, then a {@link XINSCallResult} object is returned, otherwise a
469: * {@link org.xins.common.service.CallException} is thrown.
470: *
471: * @param target
472: * the target to call, cannot be <code>null</code>.
473: *
474: * @param callConfig
475: * the call configuration, never <code>null</code>.
476: *
477: * @param request
478: * the call request to be executed, must be an instance of class
479: * {@link XINSCallRequest}, cannot be <code>null</code>.
480: *
481: * @return
482: * the result, if and only if the call succeeded, always an instance of
483: * class {@link XINSCallResult}, never <code>null</code>.
484: *
485: * @throws IllegalArgumentException
486: * if <code>request == null
487: * || callConfig == null
488: * || target == null</code>.
489: *
490: * @throws ClassCastException
491: * if the specified <code>request</code> object is not <code>null</code>
492: * and not an instance of class {@link XINSCallRequest}.
493: *
494: * @throws GenericCallException
495: * if the call attempt failed due to a generic reason.
496: * other call attempts failed as well.
497: *
498: * @throws HTTPCallException
499: * if the call attempt failed due to an HTTP-related reason.
500: *
501: * @throws XINSCallException
502: * if the call attempt failed due to a XINS-related reason.
503: */
504: public Object doCallImpl(CallRequest request,
505: CallConfig callConfig, TargetDescriptor target)
506: throws IllegalArgumentException, ClassCastException,
507: GenericCallException, HTTPCallException, XINSCallException {
508:
509: // Check preconditions
510: MandatoryArgumentChecker.check("request", request,
511: "callConfig", callConfig, "target", target);
512:
513: // Convert arguments to the appropriate classes
514: XINSCallRequest xinsRequest = (XINSCallRequest) request;
515: XINSCallConfig xinsConfig = (XINSCallConfig) callConfig;
516:
517: // Get URL, function and parameters (for logging)
518: String url = target.getURL();
519: String function = xinsRequest.getFunctionName();
520: PropertyReader p = xinsRequest.getParameters();
521: Element dataSection = xinsRequest.getDataSection();
522:
523: FormattedParameters params = new FormattedParameters(p,
524: dataSection, "", "&", 160);
525:
526: // Get the time-out values (for logging)
527: int totalTimeOut = target.getTotalTimeOut();
528: int connectionTimeOut = target.getConnectionTimeOut();
529: int socketTimeOut = target.getSocketTimeOut();
530:
531: // Log: Right before the call is performed
532: Log.log_2100(url, function, params);
533:
534: // Get the contained HTTP request from the XINS request
535: HTTPCallRequest httpRequest = xinsRequest.getHTTPCallRequest();
536:
537: // Convert XINSCallConfig to HTTPCallConfig
538: HTTPCallConfig httpConfig = xinsConfig.getHTTPCallConfig();
539:
540: // Determine the start time. Only required when an unexpected kind of
541: // exception is caught.
542: long start = System.currentTimeMillis();
543:
544: // Perform the HTTP call
545: HTTPCallResult httpResult;
546: long duration;
547: try {
548: ServiceCaller serviceCaller = (ServiceCaller) _serviceCallers
549: .get(target);
550: httpResult = (HTTPCallResult) serviceCaller.doCallImpl(
551: httpRequest, httpConfig, target);
552:
553: // Call failed due to a generic service calling error
554: } catch (GenericCallException exception) {
555: duration = exception.getDuration();
556: if (exception instanceof UnknownHostCallException) {
557: Log.log_2102(url, function, params, duration);
558: } else if (exception instanceof ConnectionRefusedCallException) {
559: Log.log_2103(url, function, params, duration);
560: } else if (exception instanceof ConnectionTimeOutCallException) {
561: Log.log_2104(url, function, params, duration,
562: connectionTimeOut);
563: } else if (exception instanceof SocketTimeOutCallException) {
564: Log.log_2105(url, function, params, duration,
565: socketTimeOut);
566: } else if (exception instanceof TotalTimeOutCallException) {
567: Log.log_2106(url, function, params, duration,
568: totalTimeOut);
569: } else if (exception instanceof IOCallException) {
570: Log
571: .log_2109(exception, url, function, params,
572: duration);
573: } else if (exception instanceof UnexpectedExceptionCallException) {
574: Log.log_2111(ExceptionUtils.getCause(exception), url,
575: function, params, duration);
576: } else {
577: String detail = "Unrecognized GenericCallException subclass "
578: + exception.getClass().getName() + '.';
579: Utils.logProgrammingError(detail);
580: }
581: throw exception;
582:
583: // Call failed due to an HTTP-related error
584: } catch (HTTPCallException exception) {
585: duration = exception.getDuration();
586: if (exception instanceof StatusCodeHTTPCallException) {
587: int code = ((StatusCodeHTTPCallException) exception)
588: .getStatusCode();
589: Log.log_2108(url, function, params, duration, code);
590: } else {
591: String detail = "Unrecognized HTTPCallException subclass "
592: + exception.getClass().getName() + '.';
593: Utils.logProgrammingError(detail);
594: }
595: throw exception;
596:
597: // Unknown kind of exception. This should never happen. Log and re-throw
598: // the exception, packed up as a CallException.
599: } catch (Throwable exception) {
600: duration = System.currentTimeMillis() - start;
601: Utils.logProgrammingError(exception);
602:
603: String message = "Unexpected exception: "
604: + exception.getClass().getName() + ". Message: "
605: + TextUtils.quote(exception.getMessage()) + '.';
606:
607: Log.log_2111(exception, url, function, params, duration);
608: throw new UnexpectedExceptionCallException(request, target,
609: duration, message, exception);
610: }
611:
612: // Determine duration
613: duration = httpResult.getDuration();
614:
615: // Make sure data was received
616: byte[] httpData = httpResult.getData();
617: if (httpData == null || httpData.length == 0) {
618:
619: // Log: No data was received
620: Log.log_2110(url, function, params, duration,
621: "No data received.");
622:
623: // Throw an appropriate exception
624: throw InvalidResultXINSCallException.noDataReceived(
625: xinsRequest, target, duration);
626: }
627:
628: // Parse the result
629: XINSCallResultData resultData;
630: try {
631: resultData = _parser.parse(httpData);
632:
633: // If parsing failed, then abort
634: } catch (ParseException e) {
635:
636: // Create a message for the new exception
637: String detail = e.getDetail();
638: String message = detail != null
639: && detail.trim().length() > 0 ? "Failed to parse result: "
640: + detail.trim()
641: : "Failed to parse result.";
642:
643: // Log: Parsing failed
644: Log.log_2110(url, function, params, duration, message);
645:
646: // Throw an appropriate exception
647: throw InvalidResultXINSCallException.parseError(httpData,
648: xinsRequest, target, duration, e);
649: }
650:
651: // If the result is unsuccessful, then throw an exception
652: String errorCode = resultData.getErrorCode();
653: if (errorCode != null) {
654:
655: boolean functionalError = false;
656: ErrorCodeSpec.Type type = null;
657: if (_capi != null) {
658: functionalError = _capi.isFunctionalError(errorCode);
659: }
660:
661: // Log this
662: if (functionalError) {
663: Log
664: .log_2115(url, function, params, duration,
665: errorCode);
666: } else {
667: Log
668: .log_2112(url, function, params, duration,
669: errorCode);
670: }
671:
672: // Standard error codes (start with an underscore)
673: if (errorCode.charAt(0) == '_') {
674: if (errorCode.equals("_DisabledFunction")) {
675: throw new DisabledFunctionException(xinsRequest,
676: target, duration, resultData);
677: } else if (errorCode.equals("_InternalError")
678: || errorCode.equals("_InvalidResponse")) {
679: throw new InternalErrorException(xinsRequest,
680: target, duration, resultData);
681: } else if (errorCode.equals("_InvalidRequest")) {
682: throw new InvalidRequestException(xinsRequest,
683: target, duration, resultData);
684: } else {
685: throw new UnacceptableErrorCodeXINSCallException(
686: xinsRequest, target, duration, resultData);
687: }
688:
689: // Non-standard error codes, CAPI not used
690: } else if (_capi == null) {
691: throw new UnsuccessfulXINSCallException(xinsRequest,
692: target, duration, resultData, null);
693:
694: // Non-standard error codes, CAPI used
695: } else {
696: AbstractCAPIErrorCodeException ex = _capi
697: .createErrorCodeException(xinsRequest, target,
698: duration, resultData);
699:
700: if (ex != null) {
701: ex.setType(type);
702: throw ex;
703: } else {
704:
705: // If the CAPI class was generated using a XINS release older
706: // than 1.2.0, then it will not override the
707: // 'createErrorCodeException' method and consequently the
708: // method will return null. It cannot be determined here
709: // whether the error code is acceptable or not
710: String ver = _capi.getXINSVersion();
711: if (ver.startsWith("0.") || ver.startsWith("1.0.")
712: || ver.startsWith("1.1.")) {
713: throw new UnsuccessfulXINSCallException(
714: xinsRequest, target, duration,
715: resultData, null);
716:
717: } else {
718: throw new UnacceptableErrorCodeXINSCallException(
719: xinsRequest, target, duration,
720: resultData);
721: }
722: }
723: }
724: }
725:
726: // Call completely succeeded
727: Log.log_2101(url, function, params, duration);
728:
729: return resultData;
730: }
731:
732: /**
733: * Constructs an appropriate <code>CallResult</code> object for a
734: * successful call attempt. This method is called from
735: * {@link #doCall(CallRequest,CallConfig)}.
736: *
737: * <p>The implementation of this method in class
738: * {@link XINSServiceCaller} expects an {@link XINSCallRequest} and
739: * returns an {@link XINSCallResult}.
740: *
741: * @param request
742: * the {@link CallRequest} that was to be executed, never
743: * <code>null</code> when called from {@link #doCall(CallRequest,CallConfig)};
744: * should be an instance of class {@link XINSCallRequest}.
745: *
746: * @param succeededTarget
747: * the {@link TargetDescriptor} for the service that was successfully
748: * called, never <code>null</code> when called from
749: * {@link #doCall(CallRequest,CallConfig)}.
750: *
751: * @param duration
752: * the call duration in milliseconds, must be a non-negative number.
753: *
754: * @param exceptions
755: * the list of {@link org.xins.common.service.CallException} instances,
756: * or <code>null</code> if there were no call failures.
757: *
758: * @param result
759: * the result from the call, which is the object returned by
760: * {@link #doCallImpl(CallRequest,CallConfig,TargetDescriptor)}, always an instance
761: * of class {@link XINSCallResult}, never <code>null</code>; .
762: *
763: * @return
764: * a {@link XINSCallResult} instance, never <code>null</code>.
765: *
766: * @throws ClassCastException
767: * if either <code>request</code> or <code>result</code> is not of the
768: * correct class.
769: */
770: protected CallResult createCallResult(CallRequest request,
771: TargetDescriptor succeededTarget, long duration,
772: CallExceptionList exceptions, Object result)
773: throws ClassCastException {
774:
775: XINSCallResult r = new XINSCallResult(
776: (XINSCallRequest) request, succeededTarget, duration,
777: exceptions, (XINSCallResultData) result);
778:
779: return r;
780: }
781:
782: /**
783: * Determines whether a call should fail-over to the next selected target
784: * based on a request, call configuration and exception list.
785: *
786: * @param request
787: * the request for the call, as passed to {@link #doCall(CallRequest,CallConfig)},
788: * should not be <code>null</code>.
789: *
790: * @param callConfig
791: * the call config that is currently in use, never <code>null</code>.
792: *
793: * @param exceptions
794: * the current list of {@link CallException}s; never <code>null</code>.
795: *
796: * @return
797: * <code>true</code> if the call should fail-over to the next target, or
798: * <code>false</code> if it should not.
799: */
800: protected boolean shouldFailOver(CallRequest request,
801: CallConfig callConfig, CallExceptionList exceptions) {
802:
803: // Get the most recent exception
804: CallException exception = exceptions.last();
805:
806: boolean should;
807:
808: // Let the superclass look at this first.
809: if (super .shouldFailOver(request, callConfig, exceptions)) {
810: should = true;
811:
812: // Otherwise check if the request may fail-over from HTTP point-of-view
813: //
814: // XXX: Note that this duplicates code that is already in the
815: // HTTPServiceCaller. This may need to be refactored at some point.
816: // It has been decided to take this approach since the
817: // shouldFailOver method in class HTTPServiceCaller has protected
818: // access.
819: // An alternative solution that should be investigated is to
820: // subclass HTTPServiceCaller.
821:
822: // A non-2xx HTTP status code indicates the request was not handled
823: } else if (exception instanceof StatusCodeHTTPCallException) {
824: int code = ((StatusCodeHTTPCallException) exception)
825: .getStatusCode();
826: should = (code < 200 || code > 299);
827:
828: // Some XINS error codes indicate the request was not accepted
829: } else if (exception instanceof UnsuccessfulXINSCallException) {
830: String s = ((UnsuccessfulXINSCallException) exception)
831: .getErrorCode();
832: should = ("_InvalidRequest".equals(s) || "_DisabledFunction"
833: .equals(s));
834:
835: // Otherwise do not fail over
836: } else {
837: should = false;
838: }
839:
840: return should;
841: }
842: }
|