Package com.zenesis.qx.remote

Source Code of com.zenesis.qx.remote.RequestHandler$MapClientId

/**
* ************************************************************************
*
*    server-objects - a contrib to the Qooxdoo project that makes server
*    and client objects operate seamlessly; like Qooxdoo, server objects
*    have properties, events, and methods all of which can be access from
*    either server or client, regardless of where the original object was
*    created.
*
*    http://qooxdoo.org
*
*    Copyright:
*      2010 Zenesis Limited, http://www.zenesis.com
*
*    License:
*      LGPL: http://www.gnu.org/licenses/lgpl.html
*      EPL: http://www.eclipse.org/org/documents/epl-v10.php
*     
*      This software is provided under the same licensing terms as Qooxdoo,
*      please see the LICENSE file in the Qooxdoo project's top-level directory
*      for details.
*
*    Authors:
*      * John Spackman (john.spackman@zenesis.com)
*
* ************************************************************************
*/
package com.zenesis.qx.remote;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import org.apache.log4j.Logger;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zenesis.qx.event.EventManager;
import com.zenesis.qx.remote.CommandId.CommandType;

/**
* Handles the request and responses for a client.
*
* This uses the Jackson JSON parser to pull data incrementally from the request; this makes the
* code harder to read/write and means that we expect the JSON data to occur in a particular
* order even though the JSON specification does not allow ordering to be enforced.  However,
* by dealing with data incrementally in the way we are able to delay deciding what type of
* data to instantiate until we have worked out where it is going - i.e. we look at the types
* of a method's parameters and use that type information to change the way we parse.  In this
* way, we can support any arbitrary mapping between JSON and Java thanks to Jackson.
*
* @author "John Spackman <john.spackman@zenesis.com>"
*
*/
public class RequestHandler {
 
  private static final Logger log = Logger.getLogger(RequestHandler.class);
 
  // Command type strings received from the client
  private static final String CMD_BOOTSTRAP = "bootstrap"// Reset application session and get bootstrap
  private static final String CMD_CALL = "call";        // Call server object method
  private static final String CMD_DISPOSE = "dispose";    // The client has disposed of a Proxied object
  private static final String CMD_EDIT_ARRAY = "edit-array"// Changes to an array
  private static final String CMD_EXPIRE = "expire";      // Expires a flushed property value
  private static final String CMD_LISTEN = "listen";      // Add an event listener
  private static final String CMD_NEW = "new";        // Create a new object
  private static final String CMD_POLL = "poll";        // Poll for changes (ie do nothing)
  private static final String CMD_SET = "set";        // Set a property value
  private static final String CMD_UNLISTEN = "unlisten";    // Remove an event listener

  // This class is sent as data by cmdNewObject to change a client ID into a server ID
  public static final class MapClientId {
    public final int serverId;
    public final int clientId;

    public MapClientId(int serverId, int clientId) {
      super();
      this.serverId = serverId;
      this.clientId = clientId;
    }
  }
 
  // This class is thrown to provide Exception information to the client
  public static class ExceptionDetails {
    public final String exceptionClass;
    public final String message;
   
    /**
     * @param exceptionClass
     * @param message
     */
    public ExceptionDetails(String exceptionClass, String message) {
      super();
      this.exceptionClass = exceptionClass;
      this.message = message;
    }
   
  }

  // This class is sent as data when an exception is thrown while setting a property value
  public static final class PropertyReset extends ExceptionDetails {
    public final Object oldValue;
   
    /**
     * @param oldValue
     * @param exceptionClass
     * @param message
     */
    public PropertyReset(Object oldValue, String exceptionClass, String message) {
      super(exceptionClass, message);
      this.oldValue = oldValue;
    }   
  }

  // RequestHandler for the current thread
  private static ThreadLocal<RequestHandler> s_currentHandler = new ThreadLocal<RequestHandler>();
 
  // Tracker for the session
  private final ProxySessionTracker tracker;
 
  // Client Objects, indexed by client ID (negative)
  private HashMap<Integer, Proxied> clientObjects;
 
  // The property currently having it's property set
  private Proxied setPropertyObject;
  private String setPropertyName;
 
  /**
   * @param tracker
   */
  public RequestHandler(ProxySessionTracker tracker) {
    super();
    this.tracker = tracker;
  }

  /**
   * Handles the callback from the client; expects either an object or an array of objects
   * @param request
   * @param response
   * @throws ServletException
   * @throws IOException
   */
  public void processRequest(Reader request, OutputStream response) throws ServletException, IOException {
    processRequest(request, new OutputStreamWriter(response));
  }

  /**
   * Handles the callback from the client; expects either an object or an array of objects
   * @param request
   * @param response
   * @throws ServletException
   * @throws IOException
   */
  public void processRequest(Reader request, Writer response) throws ServletException, IOException {
    s_currentHandler.set(this);
    ObjectMapper objectMapper = tracker.getObjectMapper();
    try {
      if (log.isDebugEnabled()) {
        StringWriter sw = new StringWriter();
        char[] buffer = new char[32 * 1024];
        int length;
        while ((length = request.read(buffer)) > 0) {
          sw.write(buffer, 0, length);
        }
        log.debug("Received: " + sw.toString());
        request = new StringReader(sw.toString());
      }
      JsonParser jp = objectMapper.getJsonFactory().createJsonParser(request);
      if (jp.nextToken() == JsonToken.START_ARRAY) {
        while(jp.nextToken() != JsonToken.END_ARRAY)
          processCommand(jp);
      } else if (jp.getCurrentToken() == JsonToken.START_OBJECT)
        processCommand(jp);
 
      if (tracker.hasDataToFlush()) {
        Writer actualResponse = response;
        if (log.isDebugEnabled()) {
          final Writer tmp = response;
          actualResponse = new Writer() {
            @Override
            public void close() throws IOException {
              tmp.close();
            }
 
            @Override
            public void flush() throws IOException {
              tmp.flush();
            }
 
            @Override
            public void write(char[] arg0, int arg1, int arg2) throws IOException {
              System.out.print(new String(arg0, arg1, arg2));
              tmp.write(arg0, arg1, arg2);
            }
          };
        }
        objectMapper.writeValue(actualResponse, tracker.getQueue());
      }
     
    } catch(ProxyTypeSerialisationException e) {
      log.fatal("Unable to serialise type information to client: " + e.getMessage(), e);
     
    } catch(ProxyException e) {
      handleException(response, objectMapper, e);
     
    } catch(Exception e) {
      log.error("Exception during callback: " + e.getMessage(), e);
      tracker.getQueue().queueCommand(CommandType.EXCEPTION, null, null, new ExceptionDetails(e.getClass().getName(), e.getMessage()));
      objectMapper.writeValue(response, tracker.getQueue());
     
    } finally {
      s_currentHandler.set(null);
    }
  }
 
  /**
   * Called to handle exceptions during processRequest
   * @param response
   * @param objectMapper
   * @param e
   * @throws IOException
   */
  protected void handleException(Writer response, ObjectMapper objectMapper, ProxyException e) throws IOException {
    tracker.getQueue().queueCommand(CommandType.EXCEPTION, e.getServerObject(), null, new ExceptionDetails(e.getClass().getName(), e.getMessage()));
    objectMapper.writeValue(response, tracker.getQueue());
  }
 
  /**
   * Returns the request handler for the current thread
   * @return
   */
  public static RequestHandler getCurrentHandler() {
    return s_currentHandler.get();
  }
 
  /**
   * Handles an object from the client; expects the object to have a property "cmd" which is
   * the type of command
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void processCommand(JsonParser jp) throws ServletException, IOException {
    String cmd = getFieldValue(jp, "cmd", String.class);
   
    if (cmd.equals(CMD_BOOTSTRAP))
      cmdBootstrap(jp);
   
    else if (cmd.equals(CMD_CALL))
      cmdCallServerMethod(jp);
   
    else if (cmd.equals(CMD_DISPOSE))
      cmdDispose(jp);
   
    else if (cmd.equals(CMD_EDIT_ARRAY))
      cmdEditArray(jp);
   
    else if (cmd.equals(CMD_EXPIRE))
      cmdExpire(jp);
   
    else if (cmd.equals(CMD_LISTEN))
      cmdAddListener(jp);
   
    else if (cmd.equals(CMD_NEW))
      cmdNewObject(jp);
   
    else if (cmd.equals(CMD_POLL))
      cmdPoll(jp);
   
    else if (cmd.equals(CMD_SET))
      cmdSetProperty(jp);
   
    else if (cmd.equals(CMD_UNLISTEN))
      cmdRemoveListener(jp);
   
    else
      throw new ServletException("Unrecognised command from client: " + cmd);
  }
 
  /**
   * Resets the application session and returns the bootstrap object to the client
   * @param jp
   */
  protected void cmdBootstrap(JsonParser jp) throws ServletException, IOException {
    tracker.resetSession();
    tracker.getQueue().queueCommand(CommandId.CommandType.BOOTSTRAP, null, null, tracker.getBootstrap());
    jp.nextToken();
  }
 
  /**
   * Handles a server method call from the client; expects a serverId, methodName, and an optional
   * array of parameters
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdCallServerMethod(JsonParser jp) throws ServletException, IOException {
    // Get the basics
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String methodName = getFieldValue(jp, "methodName", String.class);
    Proxied serverObject = getProxied(serverId);
   
   
    // Onto what should be parameters
    jp.nextToken();
   
    // Find the method by hand - we have already guaranteed that there will not be conflicting
    //  method names (ie no overridden methods) but Java needs a list of parameter types
    //  so we do it ourselves.
    boolean found = false;
    if (methodName.length() > 3 && (methodName.startsWith("get") || methodName.startsWith("set"))) {
      String name = methodName.substring(3, 4).toLowerCase();
      if (methodName.length() > 4)
        name += methodName.substring(4);
      for (ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(serverObject.getClass()); type != null; type = type.getSuperType()) {
        ProxyProperty property = type.getProperties().get(name);
        if (property != null) {
          Object result = null;
          if (methodName.startsWith("get")) {
            readParameters(jp, null);
            result = property.getValue(serverObject);
          } else {
            Object[] values = readParameters(jp, new Class[] { property.getPropertyClass().getJavaType() });
            property.setValue(serverObject, values[0]);
          }
          if (property.isOnDemand())
            tracker.setClientHasValue(serverObject, property);
          tracker.getQueue().queueCommand(CommandId.CommandType.FUNCTION_RETURN, serverObject, null, result);
          found = true;
          break;
        }
      }
    }

    if (!found)
      for (ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(serverObject.getClass()); type != null && !found; type = type.getSuperType()) {
        ProxyMethod[] methods = type.getMethods();
        for (int i = 0; i < methods.length; i++)
          if (methods[i].getName().equals(methodName)) {
            Method method = methods[i].getMethod();
           
            // Call the method
            Object[] values = null;
            try {
              values = readParameters(jp, method.getParameterTypes());
              Object result = method.invoke(serverObject, values);
              tracker.getQueue().queueCommand(CommandId.CommandType.FUNCTION_RETURN, serverObject, null, result);
            }catch(InvocationTargetException e) {
              Throwable t = e.getCause();
              log.error("Exception while invoking " + method + "(" + values + ") on " + serverObject + ": " + t.getMessage(), t);
              throw new ProxyException(serverObject, "Exception while invoking " + method + " on " + serverObject + ": " + t.getMessage(), t);
            }catch(RuntimeException e) {
              log.error("Exception while invoking " + method + "(" + values + ") on " + serverObject + ": " + e.getMessage(), e);
              throw new ProxyException(serverObject, "Exception while invoking " + method + " on " + serverObject + ": " + e.getMessage(), e);
            }catch(IllegalAccessException e) {
              throw new ServletException("Exception while running " + method + ": " + e.getMessage(), e);
            }
            found = true;
            break;
          }
      }

    if (!found)
      throw new ServletException("Cannot find method called " + methodName + " in " + serverObject);
   
    jp.nextToken();
  }
 
  private Object[] readParameters(JsonParser jp, Class[] types) throws IOException {
    if (types == null) {
      // Check for parameters
      if (jp.getCurrentToken() == JsonToken.FIELD_NAME &&
          jp.getCurrentName().equals("parameters") &&
          jp.nextToken() == JsonToken.START_ARRAY) {
        while (jp.nextToken() != JsonToken.END_ARRAY)
          ;
      }
      return null;
    }
    Object[] values = new Object[types.length];
    Object[] params = null;
   
    // Check for parameters
    if (jp.getCurrentToken() == JsonToken.FIELD_NAME &&
        jp.getCurrentName().equals("parameters") &&
        jp.nextToken() == JsonToken.START_ARRAY) {
     
      params = readArray(jp, types);
    }
   
    for (int i = 0; i < values.length; i++)
      if (i < params.length)
        values[i] = params[i];
      else
        values[i] = null;
   
    return values;
  }
 
  /**
   * Called when the client has disposed of
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdDispose(JsonParser jp) throws ServletException, IOException {
    skipFieldName(jp, "serverIds");
    while (jp.nextToken() != JsonToken.END_ARRAY) {
      int serverId = jp.readValueAs(Integer.class);
      tracker.forget(serverId);
    }
   
    jp.nextToken();
  }
 
  /**
   * Handles setting a server object property from the client; expects a serverId, propertyName, and a value
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdSetProperty(JsonParser jp) throws ServletException, IOException {
    // Get the basics
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String propertyName = getFieldValue(jp, "propertyName", String.class);
    Object value = null;

    Proxied serverObject = getProxied(serverId);
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(serverObject.getClass());
    ProxyProperty prop = getProperty(type, propertyName);
   
    skipFieldName(jp, "value");
    MetaClass propClass = prop.getPropertyClass();
    if (propClass.isSubclassOf(Proxied.class)) {
     
      if (propClass.isArray() || propClass.isCollection()) {
        value = readArray(jp, propClass.getJavaType());
       
      } else if (propClass.isMap()) {
        value = readMap(jp, propClass.getKeyClass(), propClass.getJavaType());
       
      } else {
        Integer id = jp.readValueAs(Integer.class);
        if (id != null)
          value = getProxied(id);
      }
    } else {
      value = jp.readValueAs(Object.class);
      if (value != null && Enum.class.isAssignableFrom(propClass.getJavaType())) {
        String str = camelCaseToEnum(value.toString());
        value = Enum.valueOf(propClass.getJavaType(), str);
      }
    }
   
    setPropertyValue(type, serverObject, propertyName, value);
    jp.nextToken();
  }
 
  /**
   * Sent when the client expires a cached property value, allowing the server property
   * to also its flush caches; expects a serverId and propertyName
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdExpire(JsonParser jp) throws ServletException, IOException {
    // Get the basics
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String propertyName = getFieldValue(jp, "propertyName", String.class);

    Proxied serverObject = getProxied(serverId);
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(serverObject.getClass());
    ProxyProperty prop = getProperty(type, propertyName);
    prop.expire(serverObject);
   
    jp.nextToken();
  }
 
  /**
   * Handles dynamic changes to a qa.data.Array instance without having a complete replacement; expects a
   * serverId, propertyName, type (one of "add", "remove", "order"), start, end, and optional array of items
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdEditArray(JsonParser jp) throws ServletException, IOException {
    // Get the basics
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String propertyName = getFieldValue(jp, "propertyName", String.class);
    String action = getFieldValue(jp, "type", String.class);
    Integer start = null;
    Integer end = null;
   
    if (!action.equals("replaceAll")) {
      start = getFieldValue(jp, "start", Integer.class);
      end = getFieldValue(jp, "end", Integer.class);
    }
   
    // Get our info
    Proxied serverObject = getProxied(serverId);
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(serverObject.getClass());
    ProxyProperty prop = getProperty(type, propertyName);
   
    if (prop.getPropertyClass().isMap()) {
      Map items = null;
     
      // Get the optional array of items
      if (jp.nextToken() == JsonToken.FIELD_NAME &&
          jp.getCurrentName().equals("items") &&
          jp.nextToken() == JsonToken.START_OBJECT) {
       
        items = readMap(jp, prop.getPropertyClass().getKeyClass(), prop.getPropertyClass().getJavaType());
      }
     
      // Quick logging
      if (log.isInfoEnabled()) {
        String str = "";
        if (items != null)
          for (Object key : items.keySet()) {
            if (str.length() > 0)
              str += ", ";
            str += String.valueOf(key) + "=" + String.valueOf(items.get(key));
          }
        log.info("edit-array: property=" + prop + ", type=" + action + ", start=" + start + ", end=" + end + str);
      }
     
      if (action.equals("replaceAll")) {
        Map map = (Map)prop.getValue(serverObject);
        map.clear();
        map.putAll(items);
      } else
        throw new IllegalArgumentException("Unsupported action in cmdEditArray: " + action);
     
      jp.nextToken();
    } else {
      // NOTE: items is an Array!!  But because it may be an array of primitive types, we have
      //  to use java.lang.reflect.Array to access members because we cannot cast arrays of
      //  primitives to Object[]
      Object items = null;
     
      // Get the optional array of items
      if (jp.nextToken() == JsonToken.FIELD_NAME &&
          jp.getCurrentName().equals("items") &&
          jp.nextToken() == JsonToken.START_ARRAY) {
       
        items = readArray(jp, prop.getPropertyClass().getJavaType());
      }
      int itemsLength = Array.getLength(items);
     
      // Quick logging
      if (log.isInfoEnabled()) {
        String str = "";
        if (items != null)
          for (int i = 0; i < itemsLength; i++) {
            if (str.length() != 0)
              str += ", ";
            str += Array.get(items, i);
          }
        log.info("edit-array: property=" + prop + ", type=" + action + ", start=" + start + ", end=" + end + str);
      }
     
      if (action.equals("replaceAll")) {
        if (prop.getPropertyClass().isCollection()) {
          Collection list = (Collection)prop.getValue(serverObject);
          if (list == null) {
            try {
              list = (Collection)prop.getPropertyClass().getCollectionClass().newInstance();
            }catch(Exception e) {
              throw new IllegalArgumentException(e.getMessage(), e);
            }
            prop.setValue(serverObject, list);
          }
          list.clear();
          if (items != null)
            for (int i = 0; i < itemsLength; i++)
              list.add(Array.get(items, i));
        } else {
          prop.setValue(serverObject, items);
        }
      } else
        throw new IllegalArgumentException("Unsupported action in cmdEditArray: " + action);
     
      jp.nextToken();
    }
  }
 
  /**
   * Handles creating a server object to match one created on the client; expects className,
   * clientId, properties
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdNewObject(JsonParser jp) throws ServletException, IOException {
    // Get the basics
    String className = getFieldValue(jp, "className", String.class);
    int clientId = getFieldValue(jp, "clientId", Integer.class);

    // Get the class
    Class<? extends Proxied> clazz;
    try {
      clazz = (Class<? extends Proxied>)Class.forName(className);
    } catch(ClassNotFoundException e) {
      throw new ServletException("Unknown class " + className);
    }
   
    // Create the instance
    Proxied proxied;
    try {
      proxied = clazz.newInstance();
    } catch(InstantiationException e) {
      throw new ServletException("Cannot create class " + className + ": " + e.getMessage(), e);
    } catch(IllegalAccessException e) {
      throw new ServletException("Cannot create class " + className + ": " + e.getMessage(), e);
    }
   
    // Get the server ID
    int serverId = tracker.addClientObject(proxied);
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(clazz);
   
    // Remember the client ID, in case there are subsequent commands which refer to it
    if (clientObjects == null)
      clientObjects = new HashMap<Integer, Proxied>();
    clientObjects.put(clientId, proxied);
   
    // Tell the client about the new ID - do this before changing properties
    tracker.getQueue().queueCommand(CommandId.CommandType.MAP_CLIENT_ID, proxied, null, new MapClientId(serverId, clientId));
   
    // Set property values
    if (jp.nextToken() == JsonToken.FIELD_NAME) {
      if (jp.nextToken() != JsonToken.START_OBJECT)
        throw new ServletException("Unexpected properties definiton for 'new' command");
      while (jp.nextToken() != JsonToken.END_OBJECT) {
        String propertyName = jp.getCurrentName();
        jp.nextToken();
       
        // Read a Proxied object? 
        ProxyProperty prop = getProperty(type, propertyName);
        MetaClass propClass = prop.getPropertyClass();
        Object value = null;
        if (propClass.isSubclassOf(Proxied.class)) {
         
          if (propClass.isArray() || propClass.isCollection()) {
            value = readArray(jp, propClass.getJavaType());
           
          } else if (propClass.isMap()) {
            value = readMap(jp, propClass.getKeyClass(), propClass.getJavaType());
           
          } else {
            Integer id = jp.readValueAs(Integer.class);
            if (id != null)
              value = getProxied(id);
          }
        } else {
          value = jp.readValueAs(Object.class);
          if (value != null && Enum.class.isAssignableFrom(propClass.getJavaType())) {
            String str = camelCaseToEnum(value.toString());
            value = Enum.valueOf(propClass.getJavaType(), str);
          }
        }
        setPropertyValue(type, proxied, propertyName, value);
      }
    }
   
    // Done
    jp.nextToken();
  }
 
  /**
   * Handles creating a server object to match one created on the client; expects className,
   * clientId, properties
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdPoll(JsonParser jp) throws ServletException, IOException {
    jp.nextToken();
  }
 
  /**
   * Handles adding an event listener; expects serverId, eventName
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdAddListener(JsonParser jp) throws ServletException, IOException {
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String eventName = getFieldValue(jp, "eventName", String.class);
   
    Proxied serverObject = getProxied(serverId);
    EventManager.addListener(serverObject, eventName, ProxyManager.getInstance());
    jp.nextToken();
  }
 
  /**
   * Handles removing an event listener; expects serverId, eventName
   * @param jp
   * @throws ServletException
   * @throws IOException
   */
  protected void cmdRemoveListener(JsonParser jp) throws ServletException, IOException {
    int serverId = getFieldValue(jp, "serverId", Integer.class);
    String eventName = getFieldValue(jp, "eventName", String.class);
   
    Proxied serverObject = getProxied(serverId);
    EventManager.removeListener(serverObject, eventName, ProxyManager.getInstance());
    jp.nextToken();
  }
 
  /**
   * Returns the proxied object, by serverID or client ID
   * @param id
   * @return
   */
  protected Proxied getProxied(int id) {
    Proxied proxied;
    if (id < 0)
      proxied = clientObjects.get(id);
    else
      proxied = tracker.getProxied(id);
    return proxied;
  }
 
  /**
   * Finds a property in a type, recursing up the class hierarchy
   * @param type
   * @param name
   * @return
   */
  protected ProxyProperty getProperty(ProxyType type, String name) {
    while (type != null) {
      ProxyProperty prop = type.getProperties().get(name);
      if (prop != null)
        return prop;
      type = type.getSuperType();
    }
    return null;
  }
 
  /**
   * Sets a property value, tracking which property is being set so that isSettingProperty can
   * detect recursive sets
   * @param type
   * @param proxied
   * @param propertyName
   * @param value
   */
  protected void setPropertyValue(ProxyType type, Proxied proxied, String propertyName, Object value) throws ProxyException {
    if (setPropertyObject != null || setPropertyName != null)
      throw new IllegalStateException("Recursive property setting!");
    setPropertyObject = proxied;
    setPropertyName = propertyName;
    try {
      ProxyProperty property = getProperty(type, propertyName);
     
      value = coerce(property.getPropertyClass().getJavaType(), value);
     
      if (!property.isSendExceptions())
        property.setValue(proxied, value);
      else {
        Object oldValue = property.getValue(proxied);
        try {
          property.setValue(proxied, value);
        } catch(Exception e) {
          tracker.getQueue().queueCommand(CommandId.CommandType.RESTORE_VALUE, proxied, propertyName, new PropertyReset(oldValue, e.getClass().getName(), e.getMessage()));
        }
      }
    }finally {
      setPropertyObject = null;
      setPropertyName = null;
    }
  }
 
  /**
   * Attempts to convert a native type - Jackson will interpret floating point numbers as
   * Double, which will cause an exception if the destination only accepts float.
   * @param clazz
   * @param value
   * @return
   */
  protected Object coerce(Class clazz, Object value) {
    if (value == null)
      return null;
    if (value.getClass() == clazz)
      return value;
   
    if (value.getClass() == Double.class) {
      double val = (Double)value;
      if (clazz == float.class)
        value = (float)val;
      else if (clazz == int.class)
        value = (int)val;
      else if (clazz == long.class)
        value = (long)val;
     
    } else if (value.getClass() == Long.class) {
      long val = (Long)value;
      if (clazz == float.class)
        value = (float)val;
      else if (clazz == int.class)
        value = (int)val;
      else if (clazz == long.class)
        value = (long)val;
    }
   
    return value;
  }
 
  /**
   * Detects if a property is currently being set as part of synchronising with the client
   * @param proxied
   * @param propertyName
   * @return
   */
  public boolean isSettingProperty(Proxied proxied, String propertyName) {
    if (setPropertyObject == null || setPropertyName == null)
      return false;
    return setPropertyObject == proxied && setPropertyName.equals(propertyName);
  }
 
  /**
   * Reads an array from JSON, where each value is of the listed in types; EG the first element
   * is class type[0], the second element is class type[1] etc
   * @param jp
   * @param types
   * @return
   * @throws IOException
   */
  private Object[] readArray(JsonParser jp, Class[] types) throws IOException {
    if (jp.getCurrentToken() == JsonToken.VALUE_NULL)
      return null;
   
    ArrayList result = new ArrayList();
    for (int paramIndex = 0; jp.nextToken() != JsonToken.END_ARRAY; paramIndex++) {
      Class type = null;
      if (types != null && paramIndex < types.length)
        type = types[paramIndex];
     
      if (type != null && type.isArray()) {
        if (jp.getCurrentToken() == JsonToken.VALUE_NULL)
          result.add(null);
        else if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
          Object obj = readArray(jp, type.getComponentType());
          result.add(obj);
        } else
          throw new IllegalStateException("Expected array but found " + jp.getCurrentToken());
       
      } else if (type != null && Proxied.class.isAssignableFrom(type)) {
        Integer id = jp.readValueAs(Integer.class);
        if (id != null) {
          Proxied obj = getProxied(id);
          result.add(obj);
        } else
          result.add(null);
       
      } else if (type != null && Enum.class.isAssignableFrom(type)) {
        Object obj = jp.readValueAs(Object.class);
        if (obj != null) {
          String str = camelCaseToEnum(obj.toString());
          obj = Enum.valueOf(type, str);
          result.add(obj);
        }
      } else {
        Object obj = jp.readValueAs(type != null ? type : Object.class);
        result.add(obj);
      }
    }
    return result.toArray(new Object[result.size()]);
  }
 
  /**
   * Reads an array from JSON, where each value is of the class clazz.  Note that while the result
   * is an array, you cannot assume that it is an array of Object, or use generics because generics
   * are always Objects - this is because arrays of primitive types are not arrays of Objects
   * @param jp
   * @param clazz
   * @return
   * @throws IOException
   */
  private Object readArray(JsonParser jp, Class clazz) throws IOException {
    if (jp.getCurrentToken() == JsonToken.VALUE_NULL)
      return null;
   
    boolean isProxyClass = Proxied.class.isAssignableFrom(clazz);
    ArrayList result = new ArrayList();
    for (; jp.nextToken() != JsonToken.END_ARRAY;) {
      if (isProxyClass) {
        Integer id = jp.readValueAs(Integer.class);
        if (id != null) {
          Proxied obj = getProxied(id);
          if (!clazz.isInstance(obj))
            throw new ClassCastException("Cannot cast " + obj + " class " + obj.getClass() + " to " + clazz);
          result.add(obj);
        } else
          result.add(null);
      } else {
        Object obj = readSimpleValue(jp, clazz);
        result.add(obj);
      }
    }
   
    Object arr = Array.newInstance(clazz, result.size());
    for (int i = 0; i < result.size(); i++)
      Array.set(arr, i, result.get(i));
    return arr;
    //return result.toArray(Array.newInstance(clazz, result.size()));
  }
 
  /**
   * Reads an array from JSON, where each value is of the class clazz.  Note that while the result
   * is an array, you cannot assume that it is an array of Object, or use generics because generics
   * are always Objects - this is because arrays of primitive types are not arrays of Objects
   * @param jp
   * @param clazz
   * @return
   * @throws IOException
   */
  private Map readMap(JsonParser jp, Class keyClazz, Class clazz) throws IOException {
    if (jp.getCurrentToken() == JsonToken.VALUE_NULL)
      return null;
   
    boolean isProxyClass = Proxied.class.isAssignableFrom(clazz);
    if (keyClazz == null)
      keyClazz = String.class;
    HashMap result = new HashMap();
    for (; jp.nextToken() != JsonToken.END_OBJECT;) {
      Object key = readSimpleValue(jp, keyClazz);
     
      jp.nextToken();
     
      if (isProxyClass) {
        Integer id = jp.readValueAs(Integer.class);
        if (id != null) {
          Proxied obj = getProxied(id);
          if (!clazz.isInstance(obj))
            throw new ClassCastException("Cannot cast " + obj + " class " + obj.getClass() + " to " + clazz);
          result.put(key, obj);
        } else
          result.put(key, null);
      } else {
        Object obj = readSimpleValue(jp, clazz);
        result.put(key, obj);
      }
    }
   
    return result;
  }
 
  /**
   * Reads the current token value, with special consideration for enums
   * @param jp
   * @param clazz
   * @return
   * @throws IOException
   */
  private Object readSimpleValue(JsonParser jp, Class clazz) throws IOException {
    if (jp.getCurrentToken() == JsonToken.VALUE_NULL)
      return null;
   
    Object obj = null;
    if (Enum.class.isAssignableFrom(clazz)) {
      if (jp.getCurrentToken() == JsonToken.FIELD_NAME)
        obj = jp.getCurrentName();
      else
        obj = jp.readValueAs(Object.class);
      if (obj != null) {
        String str = camelCaseToEnum(obj.toString());
        obj = Enum.valueOf(clazz, str);
      }
    } else {
      if (jp.getCurrentToken() == JsonToken.FIELD_NAME)
        obj = jp.getCurrentName();
      else
        obj = jp.readValueAs(clazz);
    }
    return obj;
  }
 
  /**
   * Gets a field value from the parser, checking that it is the type expected
   * @param <T> The desired type of object returned
   * @param jp the parser
   * @param fieldName the name of the field to get
   * @param clazz the class of the type to get
   * @return
   * @throws ServletException
   * @throws IOException
   */
  private <T> T getFieldValue(JsonParser jp, String fieldName, Class<T> clazz) throws ServletException, IOException {
    skipFieldName(jp, fieldName);

    T obj = (T)jp.readValueAs(clazz);
    return obj;
  }

  /**
   * Reads the next token and ensures that it is a field name called <code>fieldName</code>; leaves the current
   * token on the start of the field value
   * @param jp
   * @param fieldName
   * @throws ServletException
   * @throws IOException
   */
  private void skipFieldName(JsonParser jp, String fieldName) throws ServletException, IOException {
    if (jp.nextToken() != JsonToken.FIELD_NAME)
      throw new ServletException("Cannot find field name - looking for " + fieldName + " found " + jp.getCurrentToken() + ":" + jp.getText());
    String str = jp.getText();
    if (!fieldName.equals(str))
      throw new ServletException("Cannot find field called " + fieldName + " found " + str);
    jp.nextToken();
  }
 
  public static String camelCaseToEnum(String str) {
    if (str == null)
      return null;
    StringBuilder sb = new StringBuilder(str);
    char lastC = 0;
    for (int i = 0; i < sb.length(); i++) {
      char c = sb.charAt(i);
      if (Character.isUpperCase(c)) {
        if (lastC != 0 && Character.isLowerCase(lastC)) {
          sb.insert(i, '_');
          i++;
        }
      } else if (Character.isLowerCase(c))
        sb.setCharAt(i, Character.toUpperCase(c));
      lastC = c;
    }
    return sb.toString();
  }

}
TOP

Related Classes of com.zenesis.qx.remote.RequestHandler$MapClientId

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.