AbstractCAPI.java |
/* * $Id: AbstractCAPI.java,v 1.84 2013/01/22 15:13:22 agoubard Exp $ * * See the COPYRIGHT file for redistribution and use restrictions. */ package org.xins.client; import java.net.MalformedURLException; import java.net.URL; import java.security.CodeSource; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.xins.common.MandatoryArgumentChecker; import org.xins.common.collections.InvalidPropertyValueException; import org.xins.common.collections.MissingRequiredPropertyException; import org.xins.common.http.HTTPCallException; import org.xins.common.service.Descriptor; import org.xins.common.service.DescriptorBuilder; import org.xins.common.service.GenericCallException; import org.xins.common.service.TargetDescriptor; import org.xins.common.service.UnsupportedProtocolException; import org.xins.common.spec.APISpec; import org.xins.common.spec.InvalidSpecificationException; import org.xins.common.text.TextUtils; /** * Base class for generated Client-side Application Programming Interface * (CAPI) classes. * * <p><em>This class should not be derived from manually. This class is only * intended to be used as a superclass of <code>CAPI</code> classes generated * by the XINS framework.</em> * * <p><em>The constructors of this class are considered internal to XINS and * should not be used directly. The behavior of the constructors may be * changed in later releases of XINS or they may even be removed.</em> * * @version $Revision: 1.84 $ $Date: 2013/01/22 15:13:22 $ * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a> * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a> * * @since XINS 1.0.0 */ public abstract class AbstractCAPI { /** * Set of all CAPI classes for which the XINS version at build-time has * already been checked against the XINS version at run-time. Never * <code>null</code>. */ private static final Set<Class<? extends AbstractCAPI>> VERSION_COMPARISIONS_DONE = new HashSet<Class<? extends AbstractCAPI>>(); /** * The name of the API. This field cannot be <code>null</code>. */ private final String _apiName; /** * The XINS service caller to use. This field cannot be <code>null</code>. */ private final XINSServiceCaller _caller; /** * The API specification. This field is lazily initialized by * {@link #getAPISpecification()}. */ private APISpec _apiSpecification; /** * Creates a new <code>AbstractCAPI</code> object, using the specified * <code>XINSServiceCaller</code>. * * <p><em>This constructor is considered internal to XINS. Do not use it * directly.</em> * * @param descriptor * the descriptor for the service(s), cannot be <code>null</code>. * * @param callConfig * fallback configuration for the calls, or <code>null</code> if a * default should be used. * * @throws IllegalArgumentException * if <code>descriptor == null</code>. * * @throws UnsupportedProtocolException * if any of the target descriptors in <code>descriptor</code> specifies * an unsupported protocol. * * @since XINS 1.1.0 */ protected AbstractCAPI(Descriptor descriptor, XINSCallConfig callConfig) throws IllegalArgumentException, UnsupportedProtocolException { // Check preconditions MandatoryArgumentChecker.check("descriptor", descriptor); // Create and store service caller _caller = new XINSServiceCaller(descriptor, callConfig); _caller.setCAPI(this); // Determine the API name _apiName = determineAPIName(); // Compare the XINS version at build- and run-time checkXINSVersion(); } /** * Creates a new <code>AbstractCAPI</code> object, using the specified * service descriptor. * * <p>A default XINS call configuration will be used. * * <p><em>This constructor is considered internal to XINS. Do not use it * directly.</em> * * @param descriptor * the descriptor for the service(s), cannot be <code>null</code>. * * @throws IllegalArgumentException * if <code>descriptor == null</code>. * * @throws UnsupportedProtocolException * if any of the target descriptors in <code>descriptor</code> specifies * an unsupported protocol (<em>since XINS 1.1.0</em>). */ protected AbstractCAPI(Descriptor descriptor) throws IllegalArgumentException, UnsupportedProtocolException { this(descriptor, null); } /** * Creates a new <code>AbstractCAPI</code> object based on the specified * set of properties and the specified name. * * <p>A default XINS call configuration will be used. * * <p><em>This constructor is considered internal to XINS. Do not use it * directly.</em> * * @param properties * the properties to read from, cannot be <code>null</code>. * * @param apiName * the name of the API, cannot be <code>null</code> and must be a valid * API name. * * @throws IllegalArgumentException * if <code>properties == null || apiName == null</code> or if * <code>apiName</code> is not considered to be a valid API name. * * @throws MissingRequiredPropertyException * if a required property is missing in the specified properties set. * * @throws InvalidPropertyValueException * if one of the properties in the specified properties set is used to * create a <code>CAPI</code> instance but its value is considered * invalid. * * @since XINS 1.2.0 */ protected AbstractCAPI(Map<String, String> properties, String apiName) throws IllegalArgumentException, MissingRequiredPropertyException, InvalidPropertyValueException { // Check arguments MandatoryArgumentChecker.check("properties", properties, "apiName", apiName); // Determine property name String propName = "capis." + apiName; // Construct a XINS caller object _caller = new XINSServiceCaller(); // Build a descriptor from the properties Descriptor descriptor = DescriptorBuilder.build(_caller, properties, propName); // Associate caller with descriptor _caller.setDescriptor(descriptor); // Associate caller with this CAPI object _caller.setCAPI(this); // Determine the API name _apiName = determineAPIName(); // Compare the XINS version at build- and run-time checkXINSVersion(); } /** * Retrieves the name of the API (wrapper method). * * @return * the name of the API, or <code>null</code> if the name cannot be * determined. * * @since XINS 1.2.0 */ private String determineAPIName() { String apiName = getAPINameImpl(); if (! TextUtils.isEmpty(apiName)) { return apiName; } // Subclass did not return anything, determine based on package name String className = getClass().getName(); int index = className.lastIndexOf(".capi."); if (index > 0) { String s = className.substring(0, index); index = s.lastIndexOf('.'); s = s.substring(index + 1); if (! TextUtils.isEmpty(s)) { return s; } } return null; } /** * Determines the name of the API. * * @return * the name of the API, or a special indication (e.g. * <code>"<unknown>"</code>) if the name cannot be determined; * never <code>null</code>. * * @since XINS 1.2.0 */ public final String getAPIName() { if (_apiName == null) { return "<unknown>"; } else { return _apiName; } } /** * Returns <code>true</code> of the error code is a functional error code. * If unknown, false is returned. * * @param errorCode * the error code to check, cannot be <code>null</code>. * * @return * <code>true</code> if the error code is functional, <code>false</code> * if the error code is technical. */ protected boolean isFunctionalError(String errorCode) { return false; } /** * Retrieves the name of the API (implementation method). * * <p>The implementation of this method in class <code>AbstractCAPI</code> * returns <code>null</code>. * * @return * the name of the API, or <code>null</code> if unknown. * * @since XINS 1.2.0 */ protected String getAPINameImpl() { // NOTE: This method is not abstract, since that would make this class // incompatible with CAPI classes generated with older versions of // XINS (before 1.2.0) return null; } /** * Get the specification of the API. * * @return * the {@link APISpec} specification object. * * @throws InvalidSpecificationException * if the specification cannot be found or is invalid. * * @since XINS 1.3.0 */ public final APISpec getAPISpecification() throws InvalidSpecificationException { // Lazily initialize _apiSpecification if (_apiSpecification == null) { URL specsURL; CodeSource source = getClass().getProtectionDomain().getCodeSource(); if (source != null) { URL sourceURL = source.getLocation(); try { if (sourceURL.getPath().endsWith(".jar")) { specsURL = new URL("jar:" + sourceURL.toExternalForm() + "!/specs/"); } else { specsURL = new URL(sourceURL.toExternalForm() + "/specs/"); } } catch (MalformedURLException murlex) { Log.log_2116(murlex, getAPIName()); specsURL = getClass().getResource("/specs/"); } } else { specsURL = getClass().getResource("/specs/"); } _apiSpecification = new APISpec(getClass(), specsURL.toExternalForm()); } return _apiSpecification; } /** * Assigns the specified call configuration to this CAPI object. * * @param config * the call configuration to apply when executing a call with this CAPI * object, or <code>null</code> if no specific call configuration should be * associated with CAPI object; note that the call configuration can be * overridden by the request, see * {@link AbstractCAPICallRequest#configure(XINSCallConfig)}. * * @since XINS 1.2.0 */ public final void setXINSCallConfig(XINSCallConfig config) { _caller.setXINSCallConfig(config); } /** * Retrieves the call configuration currently associated with this CAPI * object. * * @return * the call configuration currently associated with this CAPI object, or * <code>null</code> if no specific call configuration is associated * with this cAPI object; note that the call configuration can be * overridden by the request, see * {@link AbstractCAPICallRequest#configuration()}. * * @since XINS 1.2.0 */ public final XINSCallConfig getXINSCallConfig() { return _caller.getXINSCallConfig(); } /** * Returns the XINS service caller to use. * * <p><em>This method is considered internal to XINS. It should not be * called directly, nor overridden.</em> * * <p><em>This method is expected to be marked <code>final</code> in XINS * 2.0. This is not done yet to remain fully compatible with XINS 1.x.</em> * * @return * the {@link XINSServiceCaller} to use, never <code>null</code>. */ protected final XINSServiceCaller getCaller() { return _caller; } /** * Checks if the XINS version used to build this CAPI class equals the * current XINS version. If not, a warning is logged. */ private void checkXINSVersion() { Class<? extends AbstractCAPI> clazz = getClass(); if (! VERSION_COMPARISIONS_DONE.contains(clazz)) { // Compare build- and run-time version of XINS String buildVersion = getXINSVersion(); String runtimeVersion = Library.getVersion(); if (! buildVersion.equals(runtimeVersion)) { Log.log_2114(_apiName, buildVersion, runtimeVersion); } // Never check this CAPI class again VERSION_COMPARISIONS_DONE.add(clazz); } } /** * Returns the version of XINS used to build this CAPI class. * * @return * the version as a {@link String}, cannot be <code>null</code>. */ public abstract String getXINSVersion(); /** * Executes the specified call request. * * <p>This method is provided for CAPI subclasses. * * @param request * the call request to execute, cannot be <code>null</code>. * * @return * the result, not <code>null</code>. * * @throws IllegalArgumentException * if <code>request == null</code>. * * @throws UnacceptableRequestException * if the request is considered to be unacceptable; this is determined * by calling * <code>request.</code>{@link AbstractCAPICallRequest#checkParameters() checkParameters()}. * * @throws GenericCallException * if the first call attempt failed due to a generic reason and all the * other call attempts (if any) failed as well. * * @throws HTTPCallException * if the first call attempt failed due to an HTTP-related reason and * all the other call attempts (if any) failed as well. * * @throws XINSCallException * if the first call attempt failed due to a XINS-related reason and * all the other call attempts (if any) failed as well. * * @since XINS 1.2.0 */ protected final XINSCallResult callImpl(AbstractCAPICallRequest request) throws IllegalArgumentException, UnacceptableRequestException, GenericCallException, HTTPCallException, XINSCallException { // Check preconditions MandatoryArgumentChecker.check("request", request); // Check whether request is acceptable UnacceptableRequestException unacceptable = request.checkParameters(); if (unacceptable != null) { throw unacceptable; } // Execute the call request XINSCallResult result = _caller.call(request.xinsCallRequest()); if (result.isNotModified()) { throw new NotModifiedException(request.xinsCallRequest(), result.getSucceededTarget(), result.getDuration()); } return result; } /** * Creates an <code>AbstractCAPIErrorCodeException</code> for the specified * error code. If the specified error code is not recognized, then * <code>null</code> is returned. * * @param request * the original request, should not be <code>null</code>. * * @param target * descriptor for the target that was attempted to be called, should not * be <code>null</code>. * * @param duration * the call duration in milliseconds, should be >= 0. * * @param resultData * the result data, should not be <code>null</code> and should have an * error code set. * * @return * if the error code is recognized, then a matching * {@link AbstractCAPIErrorCodeException} instance, otherwise * <code>null</code>. * * @throws IllegalArgumentException * if <code>request == null * || target == null * || duration < 0 * || resultData == null * || resultData.getErrorCode() == null</code>. * * @throws UnacceptableErrorCodeXINSCallException * if the specified error code is recognized but is considered * unacceptable for the function specified in the request. * * @since XINS 1.2.0 */ protected AbstractCAPIErrorCodeException createErrorCodeException(XINSCallRequest request, TargetDescriptor target, long duration, XINSCallResultData resultData) throws IllegalArgumentException, UnacceptableErrorCodeXINSCallException { // By default return nothing return null; } }