]> source.dussan.org Git - vaadin-framework.git/commitdiff
Google appengine support w/ own 'session handling' and synchronization. For #3058...
authorMarc Englund <marc.englund@itmill.com>
Fri, 4 Sep 2009 12:38:51 +0000 (12:38 +0000)
committerMarc Englund <marc.englund@itmill.com>
Fri, 4 Sep 2009 12:38:51 +0000 (12:38 +0000)
svn changeset:8669/svn branch:6.1

src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
src/com/vaadin/tests/appengine/GAESyncTest.java

index af1847a7b7bc1b8b4de307c864ad55516531da71..31d5d5ac962e29d768688ed2fd31561a003a2978 100644 (file)
@@ -361,13 +361,14 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
     protected void service(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
 
-        // check if we should serve static files (widgetsets, themes)
-        if (serveStaticResources(request, response)) {
+        RequestType requestType = getRequestType(request);
+
+        if (requestType == RequestType.STATIC_FILE) {
+            serveStaticResources(request, response);
             return;
         }
 
         Application application = null;
-        RequestType requestType = getRequestType(request);
 
         try {
             // If a duplicate "close application" URL is received for an
@@ -638,7 +639,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
      * @param requestType
      * @return true if an application should be created, false otherwise
      */
-    private boolean requestCanCreateApplication(HttpServletRequest request,
+    boolean requestCanCreateApplication(HttpServletRequest request,
             RequestType requestType) {
         if (requestType == RequestType.UIDL && isRepaintAll(request)) {
             /*
@@ -650,8 +651,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
 
         } else if (requestType == RequestType.OTHER) {
             /*
-             * TODO Should any URL request really create a new application
-             * instance if none was found?
+             * I.e URIs that are not application resources or static (theme)
+             * files.
              */
             return true;
 
@@ -890,7 +891,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
         return false;
     }
 
-    private void handleServiceSessionExpired(HttpServletRequest request,
+    void handleServiceSessionExpired(HttpServletRequest request,
             HttpServletResponse response) throws IOException, ServletException {
 
         if (isOnUnloadRequest(request)) {
@@ -1080,7 +1081,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
     }
 
     enum RequestType {
-        FILE_UPLOAD, UIDL, OTHER;
+        FILE_UPLOAD, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE;
     }
 
     protected RequestType getRequestType(HttpServletRequest request) {
@@ -1088,9 +1089,38 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
             return RequestType.FILE_UPLOAD;
         } else if (isUIDLRequest(request)) {
             return RequestType.UIDL;
+        } else if (isStaticResourceRequest(request)) {
+            return RequestType.STATIC_FILE;
+        } else if (isApplicationRequest(request)) {
+            return RequestType.APPLICATION_RESOURCE;
         }
-
         return RequestType.OTHER;
+
+    }
+
+    private boolean isApplicationRequest(HttpServletRequest request) {
+        String path = getRequestPathInfo(request);
+        if (path != null && path.startsWith("/APP/")) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isStaticResourceRequest(HttpServletRequest request) {
+        String pathInfo = request.getPathInfo();
+        if (pathInfo == null || pathInfo.length() <= 10) {
+            return false;
+        }
+
+        if ((request.getContextPath() != null)
+                && (request.getRequestURI().startsWith("/VAADIN/"))) {
+            return true;
+        } else if (request.getRequestURI().startsWith(
+                request.getContextPath() + "/VAADIN/")) {
+            return true;
+        }
+
+        return false;
     }
 
     private boolean isUIDLRequest(HttpServletRequest request) {
@@ -1830,6 +1860,13 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
                 application, assumedWindow);
     }
 
+    /**
+     * Returns the path info; note that this _can_ be different than
+     * request.getPathInfo() (e.g application runner).
+     * 
+     * @param request
+     * @return
+     */
     String getRequestPathInfo(HttpServletRequest request) {
         return request.getPathInfo();
     }
index 13d01cfb498033685cd63afc8d1077d2d08997d6..e24f405ac19938f73583fbe47ece785f3a608841 100644 (file)
@@ -1,6 +1,10 @@
 package com.vaadin.terminal.gwt.server;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.Date;
 
 import javax.servlet.ServletException;
@@ -8,64 +12,135 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
+import com.google.appengine.api.datastore.Blob;
+import com.google.appengine.api.datastore.DatastoreService;
+import com.google.appengine.api.datastore.DatastoreServiceFactory;
+import com.google.appengine.api.datastore.Entity;
+import com.google.appengine.api.datastore.EntityNotFoundException;
+import com.google.appengine.api.datastore.Key;
+import com.google.appengine.api.datastore.KeyFactory;
 import com.google.appengine.api.memcache.Expiration;
 import com.google.appengine.api.memcache.MemcacheService;
 import com.google.appengine.api.memcache.MemcacheServiceFactory;
 import com.google.apphosting.api.DeadlineExceededException;
-import com.vaadin.ui.Window;
+import com.vaadin.service.ApplicationContext;
 
 public class GAEApplicationServlet extends ApplicationServlet {
 
     private static final long serialVersionUID = 2179597952818898526L;
 
-    private static final String MUTEX_BASE = "vaadin.gae.mutex.";
+    // memcache mutex is MUTEX_BASE + sessio id
+    private static final String MUTEX_BASE = "_vmutex";
+
+    // used identify ApplicationContext in memcache and datastore
+    private static final String AC_BASE = "_vac";
+
+    // UIDL requests will attempt to gain access for this long before telling
+    // the client to retry
+    private static final int MAX_UIDL_WAIT_MILLISECONDS = 5000;
+
+    // Tell client to retry after this delay.
     // Note: currently interpreting Retry-After as ms, not sec
     private static final int RETRY_AFTER_MILLISECONDS = 100;
-    private static final int KEEP_MUTEX_MILLISECONDS = 100;
+
+    // Properties used in the datastore
+    private static final String PROPERTY_EXPIRES = "expires";
+    private static final String PROPERTY_DATA = "data";
 
     @Override
     protected void service(HttpServletRequest request,
             HttpServletResponse response) throws ServletException, IOException {
 
+        RequestType requestType = getRequestType(request);
+
+        if (requestType == RequestType.STATIC_FILE) {
+            // no locking needed, let superclass handle
+            super.service(request, response);
+            cleanSession(request);
+            return;
+        }
+
+        if (requestType == RequestType.APPLICATION_RESOURCE) {
+            // no locking needed, let superclass handle
+            getApplicationContext(request, MemcacheServiceFactory
+                    .getMemcacheService());
+            super.service(request, response);
+            cleanSession(request);
+            return;
+        }
+
+        final HttpSession session = request
+                .getSession(requestCanCreateApplication(request, requestType));
+        if (session == null) {
+            handleServiceSessionExpired(request, response);
+            cleanSession(request);
+            return;
+        }
+
         boolean locked = false;
         MemcacheService memcache = null;
-        String mutex = null;
+        String mutex = MUTEX_BASE + session.getId();
+        memcache = MemcacheServiceFactory.getMemcacheService();
         try {
-            RequestType requestType = getRequestType(request);
-            if (requestType == RequestType.UIDL) {
-                memcache = MemcacheServiceFactory.getMemcacheService();
-                mutex = MUTEX_BASE + request.getSession().getId();
-                // try to get lock
+            // try to get lock
+            long started = new Date().getTime();
+            // non-UIDL requests will try indefinitely
+            while (requestType != RequestType.UIDL
+                    || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) {
                 locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40),
                         MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
-                if (!locked) {
-                    // could not obtain lock, tell client to retry
-                    request.setAttribute("noSerialize", new Object());
-                    response
-                            .setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-                    // Note: currently interpreting Retry-After as ms, not sec
-                    response.setHeader("Retry-After", ""
-                            + RETRY_AFTER_MILLISECONDS);
-                    return;
+                if (locked) {
+                    break;
+                }
+                try {
+                    Thread.sleep(RETRY_AFTER_MILLISECONDS);
+                } catch (InterruptedException e) {
+                    System.err
+                            .println("Thread.sleep() interrupted while waiting for lock. Trying again.");
+                    e.printStackTrace(System.err);
                 }
+            }
 
+            if (!locked) {
+                // Not locked; only UIDL can get trough here unlocked: tell
+                // client to retry
+                response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                // Note: currently interpreting Retry-After as ms, not sec
+                response
+                        .setHeader("Retry-After", "" + RETRY_AFTER_MILLISECONDS);
+                return;
             }
 
+            // de-serialize or create application context, store in session
+            ApplicationContext ctx = getApplicationContext(request, memcache);
+
             super.service(request, response);
 
-            if (request.getAttribute("noSerialize") == null) {
-                // Explicitly touch session so it is re-serialized.
-                HttpSession session = request.getSession(false);
-                if (session != null) {
-                    session
-                            .setAttribute("sessionUpdated", new Date()
-                                    .getTime());
-                }
-            }
+            // serialize
+            started = new Date().getTime();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(ctx);
+            oos.flush();
+            byte[] bytes = baos.toByteArray();
+
+            started = new Date().getTime();
+
+            String id = AC_BASE + session.getId();
+            Date expire = new Date(started
+                    + (session.getMaxInactiveInterval() * 1000));
+            Expiration expires = Expiration.onDate(expire);
+
+            memcache.put(id, bytes, expires);
+
+            DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+            Entity entity = new Entity(AC_BASE, id);
+            entity.setProperty(PROPERTY_EXPIRES, expire.getTime());
+            entity.setProperty(PROPERTY_DATA, new Blob(bytes));
+            ds.put(entity);
 
         } catch (DeadlineExceededException e) {
-            System.err.println("DeadlineExceeded for "
-                    + request.getSession().getId());
+            System.err.println("DeadlineExceeded for " + session.getId());
             // TODO i18n?
             criticalNotification(
                     request,
@@ -76,21 +151,75 @@ public class GAEApplicationServlet extends ApplicationServlet {
         } finally {
             // "Next, please!"
             if (locked) {
-                memcache.delete(mutex, KEEP_MUTEX_MILLISECONDS);
+                memcache.delete(mutex);
             }
-
+            cleanSession(request);
         }
     }
 
-    protected boolean handleURI(CommunicationManager applicationManager,
-            Window window, HttpServletRequest request,
-            HttpServletResponse response) throws IOException {
+    ApplicationContext getApplicationContext(HttpServletRequest request,
+            MemcacheService memcache) {
+        HttpSession session = request.getSession();
+        String id = AC_BASE + session.getId();
+        byte[] serializedAC = (byte[]) memcache.get(id);
+        if (serializedAC == null) {
+            DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
+            Key key = KeyFactory.createKey(AC_BASE, id);
+            Entity entity = null;
+            try {
+                entity = ds.get(key);
+            } catch (EntityNotFoundException e) {
+                // Ok, we were a bit optimistic; we'll create a new one later
+            }
+            if (entity != null) {
+                Blob blob = (Blob) entity.getProperty(PROPERTY_DATA);
+                serializedAC = blob.getBytes();
+                // bring it to memcache
+                memcache.put(AC_BASE + session.getId(), serializedAC,
+                        Expiration.byDeltaSeconds(session
+                                .getMaxInactiveInterval()),
+                        MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
+            }
+        }
+        if (serializedAC != null) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(serializedAC);
+            ObjectInputStream ois;
+            try {
+                ois = new ObjectInputStream(bais);
+                ApplicationContext applicationContext = (ApplicationContext) ois
+                        .readObject();
+                session.setAttribute(WebApplicationContext.class.getName(),
+                        applicationContext);
+            } catch (IOException e) {
+                System.err
+                        .println("Could not de-serialize ApplicationContext for "
+                                + session.getId()
+                                + " A new one will be created.");
+                e.printStackTrace();
+            } catch (ClassNotFoundException e) {
+                System.err
+                        .println("Could not de-serialize ApplicationContext for "
+                                + session.getId()
+                                + " A new one will be created.");
+                e.printStackTrace();
+            }
+        }
+        // will create new context if the above did not
+        return WebApplicationContext.getApplicationContext(session);
+
+    }
 
-        if (super.handleURI(applicationManager, window, request, response)) {
-            request.setAttribute("noSerialize", new Object());
-            return true;
+    /**
+     * Removes the ApplicationContext from the session in order to minimize the
+     * data serialized to datastore and memcache.
+     * 
+     * @param request
+     */
+    private void cleanSession(HttpServletRequest request) {
+        HttpSession session = request.getSession(false);
+        if (session != null) {
+            session.removeAttribute(WebApplicationContext.class.getName());
         }
-        return false;
     }
 
 }
index 0f5c85b23932e388693dea21e82d708ee8302141..a28da32c39b1f550fa235cab985fb32c446a172c 100644 (file)
@@ -75,7 +75,7 @@ public class GAESyncTest extends Application {
                 Button b = new Button("Slow", new Button.ClickListener() {
                     public void buttonClick(ClickEvent event) {
                         try {
-                            Thread.sleep((40000));
+                            Thread.sleep(15000);
                         } catch (InterruptedException e) {
                             // TODO Auto-generated catch block
                             e.printStackTrace();