Package com.zenesis.qx.remote

Source Code of com.zenesis.qx.remote.ProxyManager

/**
* ************************************************************************
*
*    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.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.zip.GZIPOutputStream;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import com.zenesis.qx.event.Event;
import com.zenesis.qx.event.EventListener;
import com.zenesis.qx.event.EventManager;
import com.zenesis.qx.remote.CommandId.CommandType;

/**
* This class needs to be implemented by whatever software hosts the proxies
* @author John Spackman
*
*/
public class ProxyManager implements EventListener {
 
  private static final Logger log = Logger.getLogger(ProxyManager.class);

  // Singleton instance
  private static ProxyManager s_instance;
 
  // Current Tracker for this thread
  private static final ThreadLocal<ProxySessionTracker> s_currentTracker = new ThreadLocal<ProxySessionTracker>();
 
  // Trackers whose objects are synchronised between each other
  private static final ArrayList<ProxySessionTracker> s_syncedTrackers = new ArrayList<ProxySessionTracker>();
 
  // MIME type mapper, null until first use
  private static MimetypesFileTypeMap s_fileTypeMap;
 
  /**
   * Constructor; will set the singleton instance if it has not already been set
   */
  protected ProxyManager() {
    super();
    if (s_instance != null)
      throw new IllegalStateException("Cannot have multiple ProxyManager instances");
    else
      s_instance = this;
  }

  @Override
  public void handleEvent(Event event) {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return;
    getTracker().getQueue().queueCommand(CommandType.FIRE_EVENT, event.getCurrentTarget(), event.getEventName(), event.getData());
  }
 
  /**
   * Creates a temporary file
   * @param fileName
   * @return
   * @throws IOException
   */
  public File createTemporaryFile(String fileName) throws IOException {
    String body;
    String ext;
    int pos = fileName.indexOf('.');
    if (pos < 0) {
      body = fileName;
      ext = "";
    } else {
      body = fileName.substring(0, pos);
      ext = fileName.substring(pos);
    }
    File file = File.createTempFile("upload-" + body, ext);
    return file;
  }
 
  /**
   * Returns the MIME content type for a file
   * @param file
   * @return
   */
  public String getContentType(File file) {
    if (s_fileTypeMap == null)
      s_fileTypeMap = new MimetypesFileTypeMap();
    String contentType = s_fileTypeMap.getContentType(file);
    return contentType;
  }
 
  /**
   * Helper method that handles the request
   * @param request
   * @param response
   * @param bootstrapClass
   * @param appName
   * @throws ServletException
   * @throws IOException
   */
  public static void handleRequest(HttpServletRequest request, HttpServletResponse response,
      Class<? extends Proxied> bootstrapClass, String appName, boolean syncTrackers)
      throws ServletException, IOException {
   
    HttpSession session = request.getSession();
   
    ProxySessionTracker tracker = (ProxySessionTracker)session.getAttribute(appName);
    if (tracker == null) {
      tracker = new ProxySessionTracker(bootstrapClass);
      session.setAttribute(appName, tracker);
      if (syncTrackers)
        addSyncTracker(tracker);
    }
   
    // Select the tracker
    selectTracker(tracker);
    try {
      // Process the request
          String contentType = request.getContentType();
      if (request.getMethod().toUpperCase().equals("POST") && contentType != null && contentType.startsWith("multipart/form-data"))
        new UploadHandler(tracker).processUpload(request, response);
      else {
        String enc = request.getHeader("Accept-Encoding");
        response.setContentType("text/json; charset=UTF-8");
        OutputStream os = response.getOutputStream();
       
        if (enc != null) {
          /* Don't use deflate - this does not work for ajax calls on IE
          if (enc.indexOf("deflate") > -1) {
            enc = enc.indexOf("x-deflate") != -1 ? "x-deflate" : "deflate";
            os = new DeflaterOutputStream(os, new Deflater(Deflater.BEST_SPEED));
           
          } else */ if (enc.indexOf("gzip") != -1) {
            enc = enc.indexOf("x-gzip") != -1 ? "x-gzip" : "gzip";
            os = new GZIPOutputStream(os);
           
          } else
            enc = null;
        }
       
        if (enc != null)
          response.addHeader("Content-Encoding", enc);
        new RequestHandler(tracker).processRequest(request.getReader(), os);
      }
    }finally {
      // Done
      deselectTracker(tracker);
    }
  }
 
  /**
   * Selects the tracker; must be called before (de)serialisation
   * @param tracker
   * @throws IllegalArgumentException if there is already a tracker selected
   */
  public static void selectTracker(ProxySessionTracker tracker) throws IllegalArgumentException{
    if (s_currentTracker.get() != null)
      throw new IllegalArgumentException("Cannot set multiple trackers");
    s_currentTracker.set(tracker);
  }
 
  /**
   * Deselects the tracker; must be called after and (de)serialisation is complete
   * @param tracker
   * @throws IllegalArgumentException if the tracker is not the same as before
   */
  public static void deselectTracker(ProxySessionTracker tracker) throws IllegalArgumentException {
    if (s_currentTracker.get() != tracker)
      throw new IllegalArgumentException("Cannot unselect the wrong tracker, tracker=" + tracker + ", current=" + s_currentTracker.get());
    s_currentTracker.set(null);
  }
 
  /**
   * Called during de/serialisation to get the ProxyTracker
   * @return
   */
  public static ProxySessionTracker getTracker() {
    return s_currentTracker.get();
  }
 
  /**
   * Adds a synchronised Tracker
   * @param tracker
   */
  public static void addSyncTracker(ProxySessionTracker tracker) {
    synchronized(s_syncedTrackers) {
      if (s_syncedTrackers.contains(tracker))
        throw new IllegalArgumentException("Cannot add tracker more than once, tracker=" + tracker);
      s_syncedTrackers.add(tracker);
    }
  }
 
  /**
   * Removes a synchronised Tracker
   * @param tracker
   */
  public static void removeSyncTracker(ProxySessionTracker tracker) {
    synchronized(s_syncedTrackers) {
      if (!s_syncedTrackers.remove(tracker))
        throw new IllegalArgumentException("Cannot remove tracker because it does not exist, tracker=" + tracker);
    }
  }
 
  /**
   * Changes a value, but only fires the event if the value is changed
   * @param <T>
   * @param keyObject
   * @param propertyName
   * @param newValue
   * @param oldValue
   * @return
   */
  public static <T> T changeProperty(Proxied keyObject, String propertyName, T newValue, T oldValue) {
    if (newValue instanceof String) {
      if (((String) newValue).trim().length() == 0)
        newValue = null;
    }
    if (oldValue instanceof String) {
      if (((String) oldValue).trim().length() == 0)
        oldValue = null;
    }
    if (newValue == oldValue || (newValue != null && oldValue != null && newValue.equals(oldValue)))
      return oldValue;
    propertyChanged(keyObject, propertyName, newValue, oldValue);
    return newValue;
  }

  /**
   * Helper static method to register that a property has changed; this also fires a server event for
   * the property if an event is defined
   * @param proxied
   * @param propertyName
   * @param oldValue
   * @param newValue
   */
  public static void propertyChanged(Proxied keyObject, String propertyName, Object newValue, Object oldValue) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(keyObject.getClass());
    ProxyProperty property = getProperty(type, propertyName);
    if (property == null) {
      log.warn("Cannot find a property called " + propertyName + " in " + keyObject);
      return;
    }
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.propertyChanged(keyObject, property, newValue, oldValue);
    synchronized(s_syncedTrackers) {
      for (ProxySessionTracker tmp : s_syncedTrackers)
        if (tmp != tracker)
          tmp.propertyChanged(keyObject, property, newValue, oldValue);
    }
  }
 
  /**
   * Forces the value of an on demand property to be sent to the client
   * @param keyObject
   * @param propertyName
   * @param value
   */
  public static void preloadProperty(Proxied keyObject, String propertyName, Object value) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(keyObject.getClass());
    ProxyProperty property = getProperty(type, propertyName);
    if (property == null) {
      log.warn("Cannot find a property called " + propertyName + " in " + keyObject);
      return;
    }
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.preloadProperty(keyObject, property, value);
  }
 
  /**
   * Forces the value of an on demand property to be sent to the client
   * @param keyObject
   * @param propertyName
   * @param value
   */
  public static void sendProperty(Proxied keyObject, String propertyName) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(keyObject.getClass());
    ProxyProperty property = getProperty(type, propertyName);
    if (property == null) {
      log.warn("Cannot find a property called " + propertyName + " in " + keyObject);
      return;
    }
    if (!property.isOnDemand())
      return;
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.sendProperty(keyObject, property);
  }
 
  /**
   * Helper static method to register that an on-demand property has changed and it's value should be
   * expired on the client, so that the next attempt to access it causes a refresh
   * @param proxied
   * @param propertyName
   * @param oldValue
   * @param newValue
   */
  public static void expireProperty(Proxied keyObject, String propertyName) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(keyObject.getClass());
    ProxyProperty property = getProperty(type, propertyName);
    if (property == null) {
      log.warn("Cannot find a property called " + propertyName + " in " + keyObject);
      return;
    }
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.expireProperty(keyObject, property);
    synchronized(s_syncedTrackers) {
      for (ProxySessionTracker tmp : s_syncedTrackers)
        if (tmp != tracker)
          tmp.expireProperty(keyObject, property);
    }
  }
 
  /**
   * Detects whether the object has a given property
   * @param keyObject
   * @param propertyName
   * @return
   */
  public static boolean hasProperty(Proxied keyObject, String propertyName) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(keyObject.getClass());
    ProxyProperty property = getProperty(type, propertyName);
    return property != null;
  }
 
  /**
   * Loads a proxy type onto the client
   * @param clazz
   */
  public static void loadProxyType(Class<? extends Proxied> clazz) {
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(clazz);
    if (type == null)
      return;
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.loadProxyType(clazz);
  }
 
  /**
   * Loads proxy classes on the client; this is necessary if the client wants to instantiate
   * a class before the class definition has been loaded on demand.  The last part can be
   * an asterisk if all classes in a given package should be loaded
   * @param name name of the class to transfer, or array of names, or collection, etc
   */
  public static void loadProxyType(Object data) throws ClassNotFoundException {
    if (data == null)
      return;
   
    if (data.getClass().isArray()) {
      Object[] arr = (Object[])data;
      for (Object obj : arr)
        loadProxyType(obj);
     
    } else if (data instanceof Collection) {
      Collection coll = (Collection)data;
      for (Object obj : coll)
        loadProxyType(obj);
     
    } else {
      String name = data.toString();
      if (!name.endsWith(".*")) {
        Class clazz = Class.forName(name);
        if (!Proxied.class.isAssignableFrom(clazz))
          throw new IllegalArgumentException(name);
        loadProxyType(clazz);
      } else {
        ArrayList<Class> list = new ArrayList<Class>();
        name = name.substring(0, name.length() - 2);
        if (name.length() == 0)
          throw new IllegalArgumentException("Cannot return all classes to the client");
        Package pkg = Package.getPackage(name);
        if (pkg != null) {
          try {
            name = name.replace('.', '/') + "";
            Enumeration<URL> resources = ProxyManager.class.getClassLoader().getResources(name);
            while (resources.hasMoreElements()) {
              URL url = resources.nextElement();
              File dir = new File(URLDecoder.decode(url.getFile()));
              searchForClasses(list, dir, pkg.getName(), false);
            }
          }catch(IOException e) {
            log.error("Failed to access resources for " + name);
          }
        }
        for (Class clazz : list)
          loadProxyType(clazz);
      }
    }
  }
 
  /***
   * Searches for classes - there is no way to get the list of classes in a package so the only
   * way to do it is to search for .class files on the classpath
   * @param list
   * @param dir
   * @param packageName
   * @param recurse
   */
  private static void searchForClasses(final ArrayList<Class> list, File dir, final String packageName, final boolean recurse) {
    dir.listFiles(new FileFilter() {
      @Override
      public boolean accept(File file) {
        String name = file.getName();
       
        if (file.isDirectory()) {
          if (recurse && name.charAt(0) != '.') {
            searchForClasses(list, file, packageName + "." + name, recurse);
          }
         
        } else if (name.endsWith(".class")) {
          name = packageName + "." + name.substring(0, name.length() - 6);
          try {
            Class clazz = Class.forName(name);
            if (Proxied.class.isAssignableFrom(clazz))
              list.add(clazz);
          }catch(ClassNotFoundException e) {
            log.error("Could not load class " + name + ": " + e.getMessage());
          }
        }
       
        return false;
      }
    });
  }
 
  /**
   * Finds a property in a type, recursing up the class hierarchy
   * @param type
   * @param name
   * @return
   */
  protected static 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;
  }
 
  /**
   * Invalidates the client cache for the object
   * @param keyObject
   */
  public static void invalidateCache(Proxied keyObject) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.invalidateCache(keyObject);
    synchronized(s_syncedTrackers) {
      for (ProxySessionTracker tmp : s_syncedTrackers)
        if (tmp != tracker)
          tmp.invalidateCache(keyObject);
    }
  }

  /**
   * Invalidates the client cache for the objects
   * @param keyObjects
   */
  public static void invalidateCache(Proxied[] keyObjects) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null) {
      for (Proxied obj : keyObjects)
        tracker.invalidateCache(obj);
    }
    synchronized(s_syncedTrackers) {
      for (ProxySessionTracker tmp : s_syncedTrackers)
        for (Proxied obj : keyObjects)
          if (tmp != tracker)
            tmp.invalidateCache(obj);
    }
  }

  /**
   * Invalidates the client cache for the objects
   * @param keyObjects
   */
  public static void invalidateCache(Iterable list) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null) {
      for (Iterator iter = list.iterator(); iter.hasNext(); ) {
        Object obj = iter.next();
        if (obj instanceof Proxied)
          tracker.invalidateCache((Proxied)obj);
      }
    }
    synchronized(s_syncedTrackers) {
      for (ProxySessionTracker tmp : s_syncedTrackers)
        if (tmp != tracker)
          for (Iterator iter = list.iterator(); iter.hasNext(); ) {
            Object obj = iter.next();
            if (obj instanceof Proxied)
              tmp.invalidateCache((Proxied)obj);
          }
    }
  }
 
  /**
   * Forgets an object
   * @param keyObject
   */
  public static void forget(Proxied keyObject) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null)
      tracker.forget(keyObject);
  }

  /**
   * Forgets objects
   * @param keyObjects
   */
  public static void forget(Proxied[] keyObjects) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null) {
      for (Proxied obj : keyObjects)
        tracker.forget(obj);
    }
  }

  /**
   * forgets objects
   * @param keyObjects
   */
  public static void forget(Iterable list) {
    ProxySessionTracker tracker = getTracker();
    if (tracker != null) {
      for (Iterator iter = list.iterator(); iter.hasNext(); ) {
        Object obj = iter.next();
        if (obj instanceof Proxied)
          tracker.forget((Proxied)obj);
      }
    }
  }

  /**
   * Sends a class definition to the server
   * @param clazz
   */
  public static void sendClass(Class<? extends Proxied> clazz) {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return;
    CommandQueue queue = tracker.getQueue();
    ProxyType type = ProxyTypeManager.INSTANCE.getProxyType(clazz);
    queue.queueCommand(CommandId.CommandType.DEFINE, type, null, null);
  }
 
  /**
   * Helper method to fire an event remotely
   * @param event
   */
  public static void fireEvent(Event event) {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return;
    tracker.getQueue().queueCommand(CommandId.CommandType.FIRE_EVENT, event.getCurrentTarget(), event.getEventName(), null);
  }

  /**
   * Helper method to fire an event remotely
   * @param keyObject
   * @param eventName
   */
  public static void fireEvent(Object keyObject, String eventName) {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return;
    tracker.getQueue().queueCommand(CommandId.CommandType.FIRE_EVENT, keyObject, eventName, null);
  }

  /**
   * Helper method to fire an event remotely
   * @param keyObject
   * @param eventName
   * @param data
   */
  public static void fireDataEvent(Object keyObject, String eventName, Object data) {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return;
    tracker.getQueue().queueCommand(CommandId.CommandType.FIRE_EVENT, keyObject, eventName, data);
  }
 
  /**
   * Helper method to detect whether there are properties/values to be delivered
   * which are "urgent"
   * @return
   */
  public static boolean needsFlush() {
    ProxySessionTracker tracker = getTracker();
    if (tracker == null)
      return false;
    return tracker.needsFlush();
  }

  /**
   * Gets the singleton instance
   * @return
   */
  public static ProxyManager getInstance() {
    if (s_instance == null)
      new ProxyManager();
    return s_instance;
  }
 
  /**
   * Sets the singleton instance
   * @param instance
   */
  public static void setInstance(ProxyManager instance) {
    if (s_instance != null && instance != null)
      log.warn("Replacing ProxyManager " + s_instance + " with " + instance);
    s_instance = instance;
  }
}
TOP

Related Classes of com.zenesis.qx.remote.ProxyManager

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.