All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xins.server.CallingConventionManager Maven / Gradle / Ivy

The newest version!
/*
 * $Id: CallingConventionManager.java,v 1.92 2012/03/15 21:07:39 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.server;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.xins.common.Utils;
import org.xins.common.collections.InvalidPropertyValueException;
import org.xins.common.collections.MissingRequiredPropertyException;
import org.xins.common.manageable.BootstrapException;
import org.xins.common.manageable.InitializationException;
import org.xins.common.manageable.Manageable;
import org.xins.common.text.TextUtils;

/**
 * Manages the CallingConvention instances for the API.
 *
 * @version $Revision: 1.92 $ $Date: 2012/03/15 21:07:39 $
 * @author Mees Witteman
 * @author Anthony Goubard
 * @author Ernst de Haan
 *
 * @see CallingConvention
 */
class CallingConventionManager extends Manageable {

   /**
    * The name of the bootstrap property that specifies the name of the default
    * calling convention.
    */
   private static final String API_CALLING_CONVENTION_PROPERTY = "org.xins.api.calling.convention";

   /**
    * The name of the bootstrap property that specifies the class of the default
    * calling convention.
    */
   private static final String API_CALLING_CONVENTION_CLASS_PROPERTY = "org.xins.api.calling.convention.class";

   /**
    * The name of the request parameter that specifies the name of the calling
    * convention to use.
    */
   static final String CALLING_CONVENTION_PARAMETER = "_convention";

   /**
    * The name of the XINS standard calling convention.
    */
   private static final String STANDARD_CALLING_CONVENTION = "_xins-std";

   /**
    * The XINS XML calling convention.
    */
   private static final String XML_CALLING_CONVENTION = "_xins-xml";

   /**
    * The XINS XSLT calling convention.
    */
   private static final String XSLT_CALLING_CONVENTION = "_xins-xslt";

   /**
    * The name of the SOAP calling convention.
    *
    * @since XINS 1.3.0
    */
   private static final String SOAP_CALLING_CONVENTION = "_xins-soap";

   /**
    * The name of the SOAP calling convention with mapping.
    *
    * @since XINS 2.1
    */
   private static final String SOAP_MAP_CALLING_CONVENTION = "_xins-soap-map";

   /**
    * The name of the XML-RPC calling convention.
    *
    * @since XINS 1.3.0
    */
   private static final String XML_RPC_CALLING_CONVENTION = "_xins-xmlrpc";

   /**
    * The name of the JSON-RPC calling convention.
    *
    * @since XINS 2.0
    */
   public static final String JSON_RPC_CALLING_CONVENTION = "_xins-jsonrpc";

   /**
    * The name of the JSON calling convention. The call is a Yahoo! style call.
    *
    * @since XINS 2.0
    */
   private static final String JSON_CALLING_CONVENTION = "_xins-json";

   /**
    * List of the names of the calling conventions currently included in
    * XINS.
    */
   private static final List CONVENTIONS = Arrays.asList(new String[] {
      STANDARD_CALLING_CONVENTION,
      XML_CALLING_CONVENTION,
      XSLT_CALLING_CONVENTION,
      SOAP_CALLING_CONVENTION,
      SOAP_MAP_CALLING_CONVENTION,
      XML_RPC_CALLING_CONVENTION,
      JSON_RPC_CALLING_CONVENTION,
      JSON_CALLING_CONVENTION
   });

   /**
    * Array of type Class that is used when constructing a
    * CallingConvention instance via RMI.
    */
   private static final Class[] CONSTRUCTOR_ARG_CLASSES = { API.class };

   /**
    * Placeholder object used to indicate that the construction of a calling
    * convention object failed. Never null.
    */
   private static final Object CREATION_FAILED = new Object();

   /**
    * The API. Never null.
    */
   private final API _api;

   /**
    * The name of the default calling convention. There is always a default
    * calling convention (at least after bootstrapping).
    *
    * 

This field is initialized during bootstrapping. */ private String _defaultConventionName; /** * The names of the possible calling conventions. */ private List _conventionNames; /** * Map containing all calling conventions. The key is the name of the * calling convention, the value is the calling convention object, or * {@link #CREATION_FAILED} if the calling convention object could not be * constructed. */ private final HashMap _conventions; /** * Creates a CallingConventionManager for the specified API. * * @param api * the API, cannot be null. */ CallingConventionManager(API api) { // Store the reference to the API _api = api; // Fill the list of the convention names with the pre defined conventions _conventionNames = new ArrayList(); _conventionNames.addAll(CONVENTIONS); // Create a map to store the conventions in _conventions = new HashMap(12); } /** * Performs the bootstrap procedure (actual implementation). * * @param properties * the bootstrap properties, not null. * * @throws MissingRequiredPropertyException * if a required property is not given. * * @throws InvalidPropertyValueException * if the value of a certain property is invalid. * * @throws BootstrapException * if the bootstrapping failed for any other reason. */ protected void bootstrapImpl(Map properties) throws MissingRequiredPropertyException, InvalidPropertyValueException, BootstrapException { // Determine the name and class of the custom calling convention _defaultConventionName = determineDefaultConvention(properties); // Append the defined calling conventions for (String property : properties.keySet()) { if (property.startsWith(API_CALLING_CONVENTION_PROPERTY + '.') && !property.equals(API_CALLING_CONVENTION_CLASS_PROPERTY)) { String conventionName = property.substring(32, property.length() - 6); _conventionNames.add(conventionName); } } // Construct and bootstrap the default calling convention CallingConvention cc = create(properties, _defaultConventionName); // If created, store the object and attempt bootstrapping if (cc != null) { _conventions.put(_defaultConventionName, cc); bootstrap(_defaultConventionName, cc, properties); if (cc.getState() != Manageable.BOOTSTRAPPED) { throw new BootstrapException("Failed to bootstrap the default calling convention."); } // Otherwise, if it's the default calling convention, fails } else { throw new BootstrapException("Failed to create the default calling convention."); } } /** * Determines the default calling convention. * * @param properties * the bootstrap properties, cannot be null. * * @return * the name of the default calling convention, never null. * * @throws MissingRequiredPropertyException * if a required property is not given. * * @throws InvalidPropertyValueException * if the value of a certain property is invalid. */ private String determineDefaultConvention(Map properties) throws MissingRequiredPropertyException, InvalidPropertyValueException { // Name of the default calling convention (if any) String name = TextUtils.trim(properties.get(API_CALLING_CONVENTION_PROPERTY), null); // No calling convention defined if (name == null) { // Log: No custom calling convention specified Log.log_3246(); // Fallback to the XINS-specified default calling convention name = STANDARD_CALLING_CONVENTION; } // Log: Determined default calling convention Log.log_3245(name); // Return the name of the default calling convention return name; } /** * Constructs the calling convention with the specified name, using the * specified bootstrap properties. This method is called for both * regular and custom calling conventions. * *

If the name does not identify a recognized calling convention, then * null is returned. * * @param properties * the bootstrap properties, cannot be null. * * @param name * the name of the calling convention to construct, cannot be * null. * * @return * a non-bootstrapped {@link CallingConvention} instance that matches * the specified name, or null if no match is found. */ private CallingConvention create(Map properties, String name) { // Determine the name of the CallingConvention class String className; if (name.charAt(0) == '_') { className = classNameForRegular(name); } else { className = properties.get(API_CALLING_CONVENTION_PROPERTY + '.' + name + ".class"); } // If the class could not be determined, then return null if (className == null) { Log.log_3239(null, name, null); return null; } Log.log_3237(name, className); // Construct a CallingConvention instance CallingConvention cc = construct(name, className); // NOTE: Logging of construction failures is done in construct(...) // Constructed successfully if (cc != null) { cc.setAPI(_api); cc.setConventionName(name); } return cc; } /** * Determines the name of the class that represents the regular calling * convention with the specified name. A regular calling * convention is one that comes with the XINS framework. * * @param name * the name of the calling convention, should not be null * and should normally starts with an underscore character * ('_'). * * @return * the name of the {@link CallingConvention} class that matches the * specified calling convention name, or null if unknown. */ private String classNameForRegular(String name) { // XINS standard if (name.equals(STANDARD_CALLING_CONVENTION)) { return "org.xins.server.StandardCallingConvention"; // XINS XML } else if (name.equals(XML_CALLING_CONVENTION)) { return "org.xins.server.XMLCallingConvention"; // XSLT } else if (name.equals(XSLT_CALLING_CONVENTION)) { return "org.xins.server.XSLTCallingConvention"; // SOAP } else if (name.equals(SOAP_CALLING_CONVENTION)) { return "org.xins.server.SOAPCallingConvention"; // SOAP MAP } else if (name.equals(SOAP_MAP_CALLING_CONVENTION)) { return "org.xins.server.SOAPMapCallingConvention"; // XML-RPC } else if (name.equals(XML_RPC_CALLING_CONVENTION)) { return "org.xins.server.XMLRPCCallingConvention"; // JSON-RPC } else if (name.equals(JSON_RPC_CALLING_CONVENTION)) { return "org.xins.server.JSONRPCCallingConvention"; // JSON } else if (name.equals(JSON_CALLING_CONVENTION)) { return "org.xins.server.JSONCallingConvention"; // Unrecognized } else { return null; } } /** * Constructs a new CallingConvention instance by class name. * * @param name * the name of the calling convention, cannot be null. * * @param className * the name of the class, cannot be null. * * @return * the constructed {@link CallingConvention} instance, or * null if the construction failed. */ private CallingConvention construct(String name, String className) { // Try to load the class Class clazz; try { clazz = Class.forName(className, true, Utils.getContextClassLoader()); } catch (Throwable exception) { Log.log_3239(exception, name, className); return null; } // Get the constructor that accepts an API argument Constructor con = null; try { con = clazz.getConstructor(CONSTRUCTOR_ARG_CLASSES); } catch (NoSuchMethodException exception) { // fall through, do not even log } // If there is such a constructor, invoke it if (con != null) { // Invoke it Object[] args = { _api }; try { CallingConvention cc = (CallingConvention) con.newInstance(args); return cc; // If the constructor exists but failed, then construction failed } catch (Throwable exception) { Utils.logIgnoredException(exception); return null; } } // Secondly try a constructor with no arguments try { CallingConvention cc = (CallingConvention) clazz.newInstance(); return cc; } catch (Throwable exception) { Log.log_3239(exception, name, className); return null; } } /** * Bootstraps the specified calling convention. * * @param name * the name of the calling convention, cannot be null. * * @param cc * the {@link CallingConvention} object to bootstrap, cannot be * null. * * @param properties * the bootstrap properties, cannot be null. */ private void bootstrap(String name, CallingConvention cc, Map properties) { // Bootstrapping calling convention Log.log_3240(name); try { cc.bootstrap(properties); // Missing property } catch (MissingRequiredPropertyException exception) { Log.log_3242(name, exception.getPropertyName(), exception.getDetail()); // Invalid property } catch (InvalidPropertyValueException exception) { Log.log_3243(name, exception.getPropertyName(), exception.getPropertyValue(), exception.getReason()); // Catch BootstrapException and any other exceptions not caught // by previous catch statements } catch (Throwable exception) { Log.log_3244(exception, name); } } /** * Performs the initialization procedure (actual implementation). * * @param properties * the initialization properties, not null. * * @throws MissingRequiredPropertyException * if a required property is not given. * * @throws InvalidPropertyValueException * if the value of a certain property is invalid. * * @throws InitializationException * if the initialization failed, for any other reason. */ @Override protected void initImpl(Map properties) throws MissingRequiredPropertyException, InvalidPropertyValueException, InitializationException { // Loop through all CallingConvention instances for (Map.Entry entry : _conventions.entrySet()) { // Determine the name and get the CallingConvention instance String name = (String) entry.getKey(); Object cc = entry.getValue(); // Process this CallingConvention only if it was created OK if (cc != CREATION_FAILED) { // Initialize the CallingConvention CallingConvention conv = (CallingConvention) cc; init(name, conv, properties); // Fail if the *default* calling convention fails to initialize if (!conv.isUsable() && name.equals(_defaultConventionName)) { throw new InitializationException("Failed to initialize the default calling convention \"" + name + "\"."); } } } } /** * Initializes the specified calling convention. * *

If the specified calling convention is not even bootstrapped, the * initialization is not even attempted. * * @param name * the name of the calling convention, cannot be null. * * @param cc * the {@link CallingConvention} object to initialize, cannot be * null. * * @param properties * the initialization properties, cannot be null. */ private void init(String name, CallingConvention cc, Map properties) { // If the CallingConvention is not even bootstrapped, then do not even // attempt to initialize it if (! cc.isBootstrapped()) { return; } // Initialize calling convention Log.log_3435(name); try { cc.init(properties); // Missing property } catch (MissingRequiredPropertyException exception) { Log.log_3437(name, exception.getPropertyName(), exception.getDetail()); // Invalid property } catch (InvalidPropertyValueException exception) { Log.log_3438(name, exception.getPropertyName(), exception.getPropertyValue(), exception.getReason()); // Catch InitializationException and any other exceptions not caught // by previous catch statements } catch (Throwable exception) { Log.log_3439(exception, name); } } /** * Determines the calling convention to use for the specified request. * * @param request * the incoming request, cannot be null. * * @return * the calling convention to use, never null. * * @throws InvalidRequestException * if the request is considered invalid, for example because the calling * convention specified in the request is unknown. */ CallingConvention getCallingConvention(HttpServletRequest request) throws InvalidRequestException { // Get the value of the input parameter that determines the convention String ccName = request.getParameter(CALLING_CONVENTION_PARAMETER); // If a calling convention is specified then use that one if (! TextUtils.isEmpty(ccName)) { CallingConvention cc = getCallingConvention(ccName); if (! Arrays.asList(cc.getSupportedMethods(request)).contains(request.getMethod()) && !"OPTIONS".equals(request.getMethod())) { String detail = "Calling convention \"" + ccName + "\" does not support the \"" + request.getMethod() + "\" for this request."; Log.log_3507(ccName, detail); throw new InvalidRequestException(detail); } return cc; // Otherwise try to detect which one is appropriate } else { return detectCallingConvention(request); } } /** * Gets the calling convention for the given name. * *

The returned calling convention is bootstrapped and initialized. * * @param name * the name of the calling convention to retrieve, should not be * null. * * @return * the calling convention initialized, never null. * * @throws InvalidRequestException * if the calling convention name is unknown. */ private CallingConvention getCallingConvention(String name) throws InvalidRequestException { // Get the CallingConvention object Object o = _conventions.get(name); // Not found if (o == null && !_conventionNames.contains(name)) { String detail = "Calling convention \"" + name + "\" is unknown."; Log.log_3507(name, detail); throw new InvalidRequestException(detail); } else if (o == null) { // Create the asked calling convention and initiaze it CallingConvention cc = create(_api.getBootstrapProperties(), name); // If created, store the object and attempt bootstrapping if (cc != null) { o = cc; _conventions.put(name, cc); bootstrap(name, cc, _api.getBootstrapProperties()); init(name, cc, _api.getRuntimeProperties()); } else { o = CREATION_FAILED; _conventions.put(name, o); } } // Creation failed if (o == CREATION_FAILED) { String detail = "Calling convention \"" + name + "\" is known, but could not be created."; Log.log_3507(name, detail); throw new InvalidRequestException(detail); // Calling convention is recognized and was created OK } else { // Not usable (so not bootstrapped and initialized) CallingConvention cc = (CallingConvention) o; if (! cc.isUsable()) { String detail = "Calling convention \"" + name + "\" is known, but is uninitialized."; Log.log_3507(name, detail); throw new InvalidRequestException(detail); } return cc; } } /** * Gets the calling convention for the given name, or null if * the calling convention is not found or not usable. * *

The returned calling convention is bootstrapped and initialized. * * @param name * the name of the calling convention to retrieve, should not be * null. * * @return * the calling convention, or null. */ CallingConvention getCallingConvention2(String name) { try { return getCallingConvention(name); } catch (InvalidRequestException ex) { return null; } } /** * Attempts to detect which calling convention is the most appropriate for * an incoming request. This method is called when the calling convention * is not explicitly specified in the request. * *

The {@link CallingConvention#matchesRequest(HttpServletRequest)} * method is used to determine which calling conventions match. Then * the following algorithm is used to chose one: * *

    *
  • if the default calling convention matches, use that; *
  • otherwise if the {@link XSLTCallingConvention} matches and at * least one of the parameters specific for the this calling * convention is set, then use it; *
  • otherwise if the {@link StandardCallingConvention} matches, use * that; *
  • otherwise if there is exactly one other calling convention that * matches, use that one; *
  • if none of the calling conventions match, throw an * {@link InvalidRequestException}, indicating that no match could * be found; *
  • if multiple calling conventions match, throw an * {@link InvalidRequestException}, indicating that several matches * were found; *
* * @param request * the incoming request, cannot be null. * * @return * the calling convention to use, never null. * * @throws InvalidRequestException * if the request is considered invalid, for example because the calling * convention specified in the request is unknown. */ CallingConvention detectCallingConvention(HttpServletRequest request) throws InvalidRequestException { // Log: Request does not specify any calling convention Log.log_3508(); // See if the default calling convention matches CallingConvention defCC = getCallingConvention2(_defaultConventionName); if (defCC != null && defCC.matchesRequest(request)) { Log.log_3509(defCC.getClass().getName()); return defCC; } // If not, see if XSLT-specific properties are set /and/ _xins-xslt matches CallingConvention xslCC = getCallingConvention2("_xins-xslt"); if (xslCC != null && xslCC != defCC && xslCC.matchesRequest(request)) { // Determine if one of the two XSLT-specific parameters is set String p1 = request.getParameter(XSLTCallingConvention.TEMPLATE_PARAMETER); String p2 = request.getParameter(XSLTCallingConvention.CLEAR_TEMPLATE_CACHE_PARAMETER); // Use the XSLT calling convention if and only if at least one of the // parameters is actually set if (! (TextUtils.isEmpty(p1) && TextUtils.isEmpty(p2))) { Log.log_3509(XSLTCallingConvention.class.getName()); return xslCC; } } // If not, see if _xins-std matches CallingConvention stdCC = getCallingConvention2("_xins-std"); if (stdCC != null && stdCC != defCC && stdCC.matchesRequest(request)) { Log.log_3509(StandardCallingConvention.class.getName()); return stdCC; } // Local variable to hold the first matching calling convention CallingConvention matching = null; // Determine which calling conventions match for (String name : _conventionNames) { Object value = getCallingConvention2(name); // if the value is null, that's maybe an initialization problem if (value == null) { value = _conventions.get(name); } // Skip all values that are not CallingConvention instances // Skip also the default and the standard calling conventions, we // already established that they cannot handle the request if (value == CREATION_FAILED || value == defCC || value == stdCC) { continue; } // Convert the value to a CallingConvention CallingConvention cc = (CallingConvention) value; // Determine whether this one can handle it if (cc.matchesRequest(request)) { // First match if (matching == null) { matching = cc; // Fail: Multiple matches } else { Log.log_3511(); String multipleMatches = "Request does not specify a calling " + "convention, it cannot be handled by the " + "default calling convention and multiple " + "calling conventions are able to handle it: \""; String message = multipleMatches + matching.getClass().getName() + "\", \"" + cc.getClass().getName() + "\"."; throw new InvalidRequestException(message); } } } // One match if (matching != null) { return matching; // Fail: No matches } else { Log.log_3510(); String noMatches = "Request does not specify a calling convention, it " + "cannot be handled by the default calling convention and it was " + "not possible to find any calling convention that can handle it."; throw new InvalidRequestException(noMatches); } } /** * Returns the set of HTTP methods supported for function invocations. This * is the union of the methods supported by the individual calling * conventions for invoking functions, so excluding the OPTIONS * method. The latter cannot be used for function invocations, only to * determine which HTTP methods are available. See * {@link CallingConvention#getSupportedMethods()}. * * @return * the {@link Set} of supported HTTP methods, never null. * * @throws IllegalStateException * if this calling convention manager is not yet bootstrapped and * initialized, see {@link #isUsable()}. */ final Set getSupportedMethods() throws IllegalStateException { // Make sure this Manageable object is bootstrapped and initialized assertUsable(); HashSet supportedMethods = new HashSet(); for (String name : _conventionNames) { Object convention = getCallingConvention2(name); // if the value is null, that's maybe an initialization problem if (convention == null) { convention = _conventions.get(name); } // Add all methods supported by the calling convention if (convention instanceof CallingConvention) { CallingConvention cc = (CallingConvention) convention; supportedMethods.addAll(Arrays.asList(cc.getSupportedMethods())); } } return supportedMethods; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy