001: /*
002: * $Id: AbstractCAPI.java,v 1.78 2007/09/18 08:45:07 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.net.MalformedURLException;
010: import java.net.URL;
011: import java.security.CodeSource;
012: import java.util.HashSet;
013: import java.util.Set;
014:
015: import org.xins.common.MandatoryArgumentChecker;
016:
017: import org.xins.common.collections.PropertyReader;
018: import org.xins.common.collections.InvalidPropertyValueException;
019: import org.xins.common.collections.MissingRequiredPropertyException;
020:
021: import org.xins.common.http.HTTPCallException;
022:
023: import org.xins.common.service.Descriptor;
024: import org.xins.common.service.DescriptorBuilder;
025: import org.xins.common.service.GenericCallException;
026: import org.xins.common.service.TargetDescriptor;
027: import org.xins.common.service.UnsupportedProtocolException;
028:
029: import org.xins.common.spec.APISpec;
030: import org.xins.common.spec.InvalidSpecificationException;
031:
032: import org.xins.common.text.TextUtils;
033:
034: /**
035: * Base class for generated Client-side Application Programming Interface
036: * (CAPI) classes.
037: *
038: * <p><em>This class should not be derived from manually. This class is only
039: * intended to be used as a superclass of <code>CAPI</code> classes generated
040: * by the XINS framework.</em>
041: *
042: * <p><em>The constructors of this class are considered internal to XINS and
043: * should not be used directly. The behavior of the constructors may be
044: * changed in later releases of XINS or they may even be removed.</em>
045: *
046: * @version $Revision: 1.78 $ $Date: 2007/09/18 08:45:07 $
047: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
048: * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
049: *
050: * @since XINS 1.0.0
051: */
052: public abstract class AbstractCAPI {
053:
054: /**
055: * Set of all CAPI classes for which the XINS version at build-time has
056: * already been checked against the XINS version at run-time. Never
057: * <code>null</code>.
058: */
059: private static final Set VERSION_COMPARISIONS_DONE = new HashSet();
060:
061: /**
062: * The name of the API. This field cannot be <code>null</code>.
063: */
064: private final String _apiName;
065:
066: /**
067: * The XINS service caller to use. This field cannot be <code>null</code>.
068: */
069: private final XINSServiceCaller _caller;
070:
071: /**
072: * The API specification. This field is lazily initialized by
073: * {@link #getAPISpecification()}.
074: */
075: private APISpec _apiSpecification;
076:
077: /**
078: * Creates a new <code>AbstractCAPI</code> object, using the specified
079: * <code>XINSServiceCaller</code>.
080: *
081: * <p><em>This constructor is considered internal to XINS. Do not use it
082: * directly.</em>
083: *
084: * @param descriptor
085: * the descriptor for the service(s), cannot be <code>null</code>.
086: *
087: * @param callConfig
088: * fallback configuration for the calls, or <code>null</code> if a
089: * default should be used.
090: *
091: * @throws IllegalArgumentException
092: * if <code>descriptor == null</code>.
093: *
094: * @throws UnsupportedProtocolException
095: * if any of the target descriptors in <code>descriptor</code> specifies
096: * an unsupported protocol.
097: *
098: * @since XINS 1.1.0
099: */
100: protected AbstractCAPI(Descriptor descriptor,
101: XINSCallConfig callConfig) throws IllegalArgumentException,
102: UnsupportedProtocolException {
103:
104: // Check preconditions
105: MandatoryArgumentChecker.check("descriptor", descriptor);
106:
107: // Create and store service caller
108: _caller = new XINSServiceCaller(descriptor, callConfig);
109: _caller.setCAPI(this );
110:
111: // Determine the API name
112: _apiName = determineAPIName();
113:
114: // Compare the XINS version at build- and run-time
115: checkXINSVersion();
116:
117: }
118:
119: /**
120: * Creates a new <code>AbstractCAPI</code> object, using the specified
121: * service descriptor.
122: *
123: * <p>A default XINS call configuration will be used.
124: *
125: * <p><em>This constructor is considered internal to XINS. Do not use it
126: * directly.</em>
127: *
128: * @param descriptor
129: * the descriptor for the service(s), cannot be <code>null</code>.
130: *
131: * @throws IllegalArgumentException
132: * if <code>descriptor == null</code>.
133: *
134: * @throws UnsupportedProtocolException
135: * if any of the target descriptors in <code>descriptor</code> specifies
136: * an unsupported protocol (<em>since XINS 1.1.0</em>).
137: */
138: protected AbstractCAPI(Descriptor descriptor)
139: throws IllegalArgumentException,
140: UnsupportedProtocolException {
141: this (descriptor, null);
142: }
143:
144: /**
145: * Creates a new <code>AbstractCAPI</code> object based on the specified
146: * set of properties and the specified name.
147: *
148: * <p>A default XINS call configuration will be used.
149: *
150: * <p><em>This constructor is considered internal to XINS. Do not use it
151: * directly.</em>
152: *
153: * @param properties
154: * the properties to read from, cannot be <code>null</code>.
155: *
156: * @param apiName
157: * the name of the API, cannot be <code>null</code> and must be a valid
158: * API name.
159: *
160: * @throws IllegalArgumentException
161: * if <code>properties == null || apiName == null</code> or if
162: * <code>apiName</code> is not considered to be a valid API name.
163: *
164: * @throws MissingRequiredPropertyException
165: * if a required property is missing in the specified properties set.
166: *
167: * @throws InvalidPropertyValueException
168: * if one of the properties in the specified properties set is used to
169: * create a <code>CAPI</code> instance but its value is considered
170: * invalid.
171: *
172: * @since XINS 1.2.0
173: */
174: protected AbstractCAPI(PropertyReader properties, String apiName)
175: throws IllegalArgumentException,
176: MissingRequiredPropertyException,
177: InvalidPropertyValueException {
178:
179: // Check arguments
180: MandatoryArgumentChecker.check("properties", properties,
181: "apiName", apiName);
182:
183: // Determine property name
184: String propName = "capis." + apiName;
185:
186: // Construct a XINS caller object
187: _caller = new XINSServiceCaller();
188:
189: // Build a descriptor from the properties
190: Descriptor descriptor = DescriptorBuilder.build(_caller,
191: properties, propName);
192:
193: // Associate caller with descriptor
194: _caller.setDescriptor(descriptor);
195:
196: // Associate caller with this CAPI object
197: _caller.setCAPI(this );
198:
199: // Determine the API name
200: _apiName = determineAPIName();
201:
202: // Compare the XINS version at build- and run-time
203: checkXINSVersion();
204: }
205:
206: /**
207: * Retrieves the name of the API (wrapper method).
208: *
209: * @return
210: * the name of the API, or <code>null</code> if the name cannot be
211: * determined.
212: *
213: * @since XINS 1.2.0
214: */
215: private final String determineAPIName() {
216:
217: String apiName = getAPINameImpl();
218: if (!TextUtils.isEmpty(apiName)) {
219: return apiName;
220: }
221:
222: // Subclass did not return anything, determine based on package name
223: String className = getClass().getName();
224: int index = className.lastIndexOf(".capi.");
225: if (index > 0) {
226: String s = className.substring(0, index);
227: index = s.lastIndexOf('.');
228: s = s.substring(index + 1);
229: if (!TextUtils.isEmpty(s)) {
230: return s;
231: }
232: }
233:
234: return null;
235: }
236:
237: /**
238: * Determines the name of the API.
239: *
240: * @return
241: * the name of the API, or a special indication (e.g.
242: * <code>"<unknown>"</code>) if the name cannot be determined;
243: * never <code>null</code>.
244: *
245: * @since XINS 1.2.0
246: */
247: public final String getAPIName() {
248: if (_apiName == null) {
249: return "<unknown>";
250: } else {
251: return _apiName;
252: }
253: }
254:
255: /**
256: * Returns <code>true</code> of the error code is a functional error code.
257: * If unknown, false is returned.
258: *
259: * @param errorCode
260: * the error code to check, cannot be <code>null</code>.
261: *
262: * @return
263: * <code>true</code> if the error code is functional, <code>false</code>
264: * if the error code is technical.
265: */
266: protected boolean isFunctionalError(String errorCode) {
267: return false;
268: }
269:
270: /**
271: * Retrieves the name of the API (implementation method).
272: *
273: * <p>The implementation of this method in class <code>AbstractCAPI</code>
274: * returns <code>null</code>.
275: *
276: * @return
277: * the name of the API, or <code>null</code> if unknown.
278: *
279: * @since XINS 1.2.0
280: */
281: protected String getAPINameImpl() {
282:
283: // NOTE: This method is not abstract, since that would make this class
284: // incompatible with CAPI classes generated with older versions of
285: // XINS (before 1.2.0)
286:
287: return null;
288: }
289:
290: /**
291: * Get the specification of the API.
292: *
293: * @return
294: * the {@link APISpec} specification object.
295: *
296: * @throws InvalidSpecificationException
297: * if the specification cannot be found or is invalid.
298: *
299: * @since XINS 1.3.0
300: */
301: public final APISpec getAPISpecification()
302: throws InvalidSpecificationException {
303:
304: // Lazily initialize _apiSpecification
305: if (_apiSpecification == null) {
306: URL specsURL;
307: CodeSource source = getClass().getProtectionDomain()
308: .getCodeSource();
309: if (source != null) {
310: URL sourceURL = source.getLocation();
311: try {
312: if (sourceURL.getPath().endsWith(".jar")) {
313: specsURL = new URL("jar:"
314: + sourceURL.toExternalForm()
315: + "!/specs/");
316: } else {
317: specsURL = new URL(sourceURL.toExternalForm()
318: + "/specs/");
319: }
320: } catch (MalformedURLException murlex) {
321: Log.log_2116(murlex, getAPIName());
322: specsURL = getClass().getResource("/specs/");
323: }
324: } else {
325: specsURL = getClass().getResource("/specs/");
326: }
327: _apiSpecification = new APISpec(getClass(), specsURL
328: .toExternalForm());
329: }
330:
331: return _apiSpecification;
332: }
333:
334: /**
335: * Assigns the specified call configuration to this CAPI object.
336: *
337: * @param config
338: * the call configuration to apply when executing a call with this CAPI
339: * object, or <code>null</code> if no specific call configuration should be
340: * associated with CAPI object; note that the call configuration can be
341: * overridden by the request, see
342: * {@link AbstractCAPICallRequest#configure(XINSCallConfig)}.
343: *
344: * @since XINS 1.2.0
345: */
346: public final void setXINSCallConfig(XINSCallConfig config) {
347: _caller.setXINSCallConfig(config);
348: }
349:
350: /**
351: * Retrieves the call configuration currently associated with this CAPI
352: * object.
353: *
354: * @return
355: * the call configuration currently associated with this CAPI object, or
356: * <code>null</code> if no specific call configuration is associated
357: * with this cAPI object; note that the call configuration can be
358: * overridden by the request, see
359: * {@link AbstractCAPICallRequest#configuration()}.
360: *
361: * @since XINS 1.2.0
362: */
363: public final XINSCallConfig getXINSCallConfig() {
364: return _caller.getXINSCallConfig();
365: }
366:
367: /**
368: * Returns the XINS service caller to use.
369: *
370: * <p><em>This method is considered internal to XINS. It should not be
371: * called directly, nor overridden.</em>
372: *
373: * <p><em>This method is expected to be marked <code>final</code> in XINS
374: * 2.0. This is not done yet to remain fully compatible with XINS 1.x.</em>
375: *
376: * @return
377: * the {@link XINSServiceCaller} to use, never <code>null</code>.
378: */
379: protected final XINSServiceCaller getCaller() {
380: return _caller;
381: }
382:
383: /**
384: * Checks if the XINS version used to build this CAPI class equals the
385: * current XINS version. If not, a warning is logged.
386: */
387: private void checkXINSVersion() {
388:
389: Class clazz = getClass();
390: if (!VERSION_COMPARISIONS_DONE.contains(clazz)) {
391:
392: // Compare build- and run-time version of XINS
393: String buildVersion = getXINSVersion();
394: String runtimeVersion = Library.getVersion();
395: if (!buildVersion.equals(runtimeVersion)) {
396: Log.log_2114(_apiName, buildVersion, runtimeVersion);
397: }
398:
399: // Never check this CAPI class again
400: VERSION_COMPARISIONS_DONE.add(clazz);
401: }
402: }
403:
404: /**
405: * Returns the version of XINS used to build this CAPI class.
406: *
407: * @return
408: * the version as a {@link String}, cannot be <code>null</code>.
409: */
410: public abstract String getXINSVersion();
411:
412: /**
413: * Executes the specified call request.
414: *
415: * <p>This method is provided for CAPI subclasses.
416: *
417: * @param request
418: * the call request to execute, cannot be <code>null</code>.
419: *
420: * @return
421: * the result, not <code>null</code>.
422: *
423: * @throws IllegalArgumentException
424: * if <code>request == null</code>.
425: *
426: * @throws UnacceptableRequestException
427: * if the request is considered to be unacceptable; this is determined
428: * by calling
429: * <code>request.</code>{@link AbstractCAPICallRequest#checkParameters() checkParameters()}.
430: *
431: * @throws GenericCallException
432: * if the first call attempt failed due to a generic reason and all the
433: * other call attempts (if any) failed as well.
434: *
435: * @throws HTTPCallException
436: * if the first call attempt failed due to an HTTP-related reason and
437: * all the other call attempts (if any) failed as well.
438: *
439: * @throws XINSCallException
440: * if the first call attempt failed due to a XINS-related reason and
441: * all the other call attempts (if any) failed as well.
442: *
443: * @since XINS 1.2.0
444: */
445: protected final XINSCallResult callImpl(
446: AbstractCAPICallRequest request)
447: throws IllegalArgumentException,
448: UnacceptableRequestException, GenericCallException,
449: HTTPCallException, XINSCallException {
450:
451: // Check preconditions
452: MandatoryArgumentChecker.check("request", request);
453:
454: // Check whether request is acceptable
455: UnacceptableRequestException unacceptable = request
456: .checkParameters();
457: if (unacceptable != null) {
458: throw unacceptable;
459: }
460:
461: // Execute the call request
462: return _caller.call(request.xinsCallRequest());
463: }
464:
465: /**
466: * Creates an <code>AbstractCAPIErrorCodeException</code> for the specified
467: * error code. If the specified error code is not recognized, then
468: * <code>null</code> is returned.
469: *
470: * @param request
471: * the original request, should not be <code>null</code>.
472: *
473: * @param target
474: * descriptor for the target that was attempted to be called, should not
475: * be <code>null</code>.
476: *
477: * @param duration
478: * the call duration in milliseconds, should be >= 0.
479: *
480: * @param resultData
481: * the result data, should not be <code>null</code> and should have an
482: * error code set.
483: *
484: * @return
485: * if the error code is recognized, then a matching
486: * {@link AbstractCAPIErrorCodeException} instance, otherwise
487: * <code>null</code>.
488: *
489: * @throws IllegalArgumentException
490: * if <code>request == null
491: * || target == null
492: * || duration < 0
493: * || resultData == null
494: * || resultData.getErrorCode() == null</code>.
495: *
496: * @throws UnacceptableErrorCodeXINSCallException
497: * if the specified error code is recognized but is considered
498: * unacceptable for the function specified in the request.
499: *
500: * @since XINS 1.2.0
501: */
502: protected AbstractCAPIErrorCodeException createErrorCodeException(
503: XINSCallRequest request, TargetDescriptor target,
504: long duration, XINSCallResultData resultData)
505: throws IllegalArgumentException,
506: UnacceptableErrorCodeXINSCallException {
507:
508: // By default return nothing
509: return null;
510: }
511: }
|