Package er.ajax.json

Source Code of er.ajax.json.JSONRequestHandler$LRUMap

package er.ajax.json;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;

import org.jabsorb.JSONRPCBridge;
import org.jabsorb.JSONRPCResult;
import org.jabsorb.callback.InvocationCallback;
import org.jabsorb.serializer.Serializer;
import org.json.JSONException;
import org.json.JSONObject;

import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WODynamicURL;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WORequestHandler;
import com.webobjects.appserver.WOResponse;
import com.webobjects.appserver.WOSession;
import com.webobjects.foundation._NSUtilities;

import er.extensions.foundation.ERXMutableURL;
import er.extensions.foundation.ERXProperties;

/**
* JSONRequestHandler provides support for JSON RPC services that can be both
* stateless or stateful (using JSON Components).
*  
* @author mschrag
* @property er.ajax.json.globalBacktrackCacheSize the maximum number of global components that can be in the session (defaults to backtrack cache size)
* @property er.ajax.json.backtrackCacheSize the maximum number of non-global components that can be in the session  (defaults to backtrack cache size)
*/
public class JSONRequestHandler extends WORequestHandler {
  public static final String RequestHandlerKey = "json";

  private JSONRPCBridge _sharedBridge;

  /**
   * Registers the JSONRequestHandler with your application using the default key.
   *
   * @return the request handler instance
   */
  public static JSONRequestHandler register() {
    JSONRequestHandler requestHandler = new JSONRequestHandler();
    WOApplication.application().registerRequestHandler(requestHandler, JSONRequestHandler.RequestHandlerKey);
    return requestHandler;
  }

  /**
   * Creates a new JSONRequestHandler.
   */
  public JSONRequestHandler() {
    _sharedBridge = JSONBridge.createBridge();
  }

  /**
   * Returns the shared JSON Bridge for this request handler.
   *
   * @return the shared JSON Bridge for this request handler
   */
  public JSONRPCBridge getJSONBridge() {
    return _sharedBridge;
  }

  /**
   * Registers a custom serializer into the global JSON serializers (see JSONRPCBridge).
   * 
   * @param serializer the serializer to register
   * @throws Exception if the registration fails
   */
  public static void registerSerializer(Serializer serializer) throws Exception {
    JSONRPCBridge.getSerializer().registerSerializer(serializer);
  }

  /**
   * Registers all of the methods of the given class to be available for services to call (see JSONRPCBridge).
   *
   * @param clazz the class to register
   * @throws Exception if the registration fails
   */
  public static void registerClass(Class clazz) throws Exception {
    JSONRequestHandler.registerClass(clazz.getSimpleName(), clazz);
  }

  /**
   * Registers all of the methods of the given class to be available for services to call (see JSONRPCBridge).
   *
   * @param name the namespace to register the methods under
   * @param clazz the class to register
   * @throws Exception if the registration fails
   */
  public static void registerClass(String name, Class clazz) throws Exception {
    JSONRPCBridge.getGlobalBridge().registerClass(name, clazz);
  }

  /**
   * Registers the given object in the shared JSON bridge.  The shared JSON
   * bridge is used for stateless JSON services.  As an example, if you call
   * registerService("myExampleService", new ExampleService()) you can then
   * call json.myExampleService.someMethodInExampleService from your Javascript.
   * The same instance is shared across all of your service users, so you should
   * not store any state in this class.
   *
   * @param name the name to register the object as
   * @param serviceObject the instance to register
   */
  public void registerService(String name, Object serviceObject) {
    _sharedBridge.registerObject(name, serviceObject);
  }

  /**
   * Returns a URL pointing to the JSON request handler.  This variant
   * should be used for the shared web service endpoint.
   *
   * @param context the current WOContext
   * @param queryString the query string to append
   * @return a JSON request handler URL
   */
  public static String jsonUrl(WOContext context, String queryString) {
    return JSONRequestHandler.jsonUrl(context, JSONRequestHandler.RequestHandlerKey, null, null, queryString);
  }

  /**
   * Returns a URL pointing to the JSON request handler.  This variant
   * should be used for the shared web service endpoint.
   *
   * @param context the current WOContext
   * @param requestHandlerKey if you registered a custom JSON request handler key
   * @param queryString the query string to append
   * @return a JSON request handler URL
   */
  public static String jsonUrl(WOContext context, String requestHandlerKey, String queryString) {
    return JSONRequestHandler.jsonUrl(context, requestHandlerKey, null, null, queryString);
  }

  /**
   * Returns a URL pointing to the JSON request handler for a JSON component.
   *
   * @param context the current WOContext
   * @param componentName the name of the component to lookup
   * @param instance the instance identifier (any value) to create a unique instance (or null for a session-global)
   * @param queryString the query string to append
   * @return a JSON request handler URL
   */
  public static String jsonUrl(WOContext context, String componentName, String instance, String queryString) {
    return JSONRequestHandler.jsonUrl(context, JSONRequestHandler.RequestHandlerKey, componentName, instance, queryString);
  }

  /**
   * Returns a URL pointing to the JSON request handler.
   *
   * @param context the current WOContext
   * @param requestHandlerKey if you registered a custom JSON request handler key
   * @param componentName the name of the component to lookup (or null for the shared bridge)
   * @param componentInstance the instance identifier (any value) to create a unique instance (or null for a session-global)
   * @param queryString the query string to append
   * @return a JSON request handler URL
   */
  public static String jsonUrl(WOContext context, String requestHandlerKey, String componentName, String componentInstance, String queryString) {
    String componentNameAndInstance;
    if (componentName == null) {
      componentNameAndInstance = "";
    }
    else {
      componentNameAndInstance = JSONRequestHandler.componentNameAndInstance(componentName, componentInstance);
    }
    return JSONRequestHandler._jsonUrl(context, requestHandlerKey, componentNameAndInstance, queryString);
  }

  /**
   * Returns a URL pointing to the JSON request handler.
   *
   * @param context the current WOContext
   * @param requestHandlerKey if you registered a custom JSON request handler key
   * @param componentNameAndInstance the name/instance identifier of the component to lookup (or null for the shared bridge)
   * @param queryString the query string to append
   * @return a JSON request handler URL
   */
  public static String _jsonUrl(WOContext context, String requestHandlerKey, String componentNameAndInstance, String queryString) {
    String jsonUrl = context.urlWithRequestHandlerKey(JSONRequestHandler.RequestHandlerKey, componentNameAndInstance, queryString);
    return jsonUrl;
  }

  @SuppressWarnings("unchecked")
  @Override
  public WOResponse handleRequest(WORequest request) {
    WOApplication application = WOApplication.application();
    application.awake();
    try {
      WOContext context = application.createContextForRequest(request);
      WOResponse response = application.createResponseInContext(context);

      Object output;
      try {
        String inputString = request.contentString();
        JSONObject input = new JSONObject(inputString);
        String sessionIdKey = WOApplication.application().sessionIdKey();
        String sessionId = request.cookieValueForKey(sessionIdKey);
        if (sessionId == null) {
          ERXMutableURL url = new ERXMutableURL();
          url.setQueryParameters(request.queryString());
          sessionId = url.queryParameter(sessionIdKey);
          if (sessionId == null && input.has(sessionIdKey)) {
            sessionId = input.getString(sessionIdKey);
          }
        }
        context._setRequestSessionID(sessionId);
        WOSession session = null;
        if (context._requestSessionID() != null) {
          session = WOApplication.application().restoreSessionWithID(sessionId, context);
        }
        if (session != null) {
          session.awake();
        }
        try {
          JSONComponentCallback componentCallback = null;
         
          WODynamicURL url = request._uriDecomposed();
          String requestHandlerPath = url.requestHandlerPath();
          JSONRPCBridge jsonBridge;
          if (requestHandlerPath != null && requestHandlerPath.length() > 0) {
            String componentNameAndInstance = requestHandlerPath;
            String componentInstance;
            String componentName;
            int slashIndex = componentNameAndInstance.indexOf('/');
            if (slashIndex == -1) {
              componentName = componentNameAndInstance;
              componentInstance = null;
            }
            else {
              componentName = componentNameAndInstance.substring(0, slashIndex);
              componentInstance = componentNameAndInstance.substring(slashIndex + 1);
            }

            if (session == null) {
              session = context.session();
            }

            String bridgesKey = (componentInstance == null) ? "_JSONGlobalBridges" : "_JSONInstanceBridges";
            Map<String, JSONRPCBridge> componentBridges = (Map<String, JSONRPCBridge>) session.objectForKey(bridgesKey);
            if (componentBridges == null) {
              int limit = ERXProperties.intForKeyWithDefault((componentInstance == null) ? "er.ajax.json.globalBacktrackCacheSize" : "er.ajax.json.backtrackCacheSize", WOApplication.application().pageCacheSize());
              componentBridges = new LRUMap<String, JSONRPCBridge>(limit);
              session.setObjectForKey(componentBridges, bridgesKey);
            }
            jsonBridge = componentBridges.get(componentNameAndInstance);
            if (jsonBridge == null) {
              Class componentClass = _NSUtilities.classWithName(componentName);
              JSONComponent component;
              if (JSONComponent.class.isAssignableFrom(componentClass)) {
                component = (JSONComponent) _NSUtilities.instantiateObject(componentClass, new Class[] { WOContext.class }, new Object[] { context }, true, false);
              }
              else {
                throw new SecurityException("There is no JSON component named '" + componentName + "'.");
              }
              jsonBridge = createBridgeForComponent(component, componentName, componentInstance, componentBridges);
            }
           
            componentCallback = new JSONComponentCallback(context);
            jsonBridge.registerCallback(componentCallback, WOContext.class);
          }
          else {
            jsonBridge = _sharedBridge;
          }

          try {
            output = jsonBridge.call(new Object[] { request, response, context }, input);
          }
          finally {
            if (componentCallback != null) {
              jsonBridge.unregisterCallback(componentCallback, WOContext.class);
            }
          }

          if (context._session() != null) {
            WOSession contextSession = context._session();
            // If this is a new session, then we have to force it to be a cookie session
            if (sessionId == null) {
              boolean storesIDsInCookies = contextSession.storesIDsInCookies();
              try {
                contextSession.setStoresIDsInCookies(true);
                contextSession._appendCookieToResponse(response);
              }
              finally {
                contextSession.setStoresIDsInCookies(storesIDsInCookies);
              }
            }
            else {
              contextSession._appendCookieToResponse(response);
            }
          }
          response.appendContentString(output.toString());
          response._finalizeInContext(context);
          response.disableClientCaching();
        }
        finally {
          try {
            if (session != null) {
              session.sleep();
            }
          }
          finally {
            if (context._session() != null) {
              WOApplication.application().saveSessionForContext(context);
            }
          }
        }
      }
      catch (NoSuchElementException e) {
        e.printStackTrace();
        output = new JSONRPCResult(JSONRPCResult.CODE_ERR_NOMETHOD, null, JSONRPCResult.MSG_ERR_NOMETHOD);
      }
      catch (JSONException e) {
        e.printStackTrace();
        output = new JSONRPCResult(JSONRPCResult.CODE_ERR_PARSE, null, JSONRPCResult.MSG_ERR_PARSE);
      }
      catch (Throwable t) {
        t.printStackTrace();
        output = new JSONRPCResult(JSONRPCResult.CODE_ERR_PARSE, null, t.getMessage());
      }

      return response;
    }
    finally {
      application.sleep();
    }
  }

  protected static String componentNameAndInstance(String componentName, String componentInstance) {
    String componentNameAndInstance;
    if (componentInstance == null) {
      componentNameAndInstance = componentName;
    }
    else {
      componentNameAndInstance = componentName + "/" + componentInstance;
    }
    return componentNameAndInstance;
  }

  protected JSONRPCBridge createBridgeForComponent(JSONComponent component, String componentName, String componentInstance, Map<String, JSONRPCBridge> componentBridges) throws Exception {
    JSONRPCBridge jsonBridge = JSONBridge.createBridge();
    jsonBridge.registerCallableReference(JSONComponent.class);
    jsonBridge.registerObject("component", component);
    String componentNameAndInstance = JSONRequestHandler.componentNameAndInstance(componentName, componentInstance);
    componentBridges.put(componentNameAndInstance, jsonBridge);
    return jsonBridge;
  }

  protected static class LRUMap<U, V> extends LinkedHashMap<U, V> {
    /**
     * Do I need to update serialVersionUID?
     * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
     * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
     */
    private static final long serialVersionUID = 1L;

    private int _maxSize;

    public LRUMap(int maxSize) {
      super(16, 0.75f, true);
      _maxSize = maxSize;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<U, V> eldest) {
      return size() > _maxSize;
    }
  }

  protected static class JSONComponentCallback implements InvocationCallback {
    /**
     * Do I need to update serialVersionUID?
     * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
     * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
     */
    private static final long serialVersionUID = 1L;

    private WOContext _context;

    public JSONComponentCallback(WOContext context) {
      _context = context;
    }

    public void preInvoke(Object context, Object instance, Method method, Object[] arguments) throws Exception {
      if (instance instanceof JSONComponent) {
        JSONComponent component = (JSONComponent) instance;
        component._setContext(_context);
        component.checkAccess();
      }
    }

    public void postInvoke(Object context, Object instance, Method method, Object result) throws Exception {
      // DO NOTHING
    }
  }
}
TOP

Related Classes of er.ajax.json.JSONRequestHandler$LRUMap

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.