]> source.dussan.org Git - vaadin-framework.git/commitdiff
Appengine fixes; This version uses GAE sessions, but attempts to keep synchronization...
authorMarc Englund <marc.englund@itmill.com>
Tue, 1 Sep 2009 11:51:58 +0000 (11:51 +0000)
committerMarc Englund <marc.englund@itmill.com>
Tue, 1 Sep 2009 11:51:58 +0000 (11:51 +0000)
svn changeset:8614/svn branch:6.1

WebContent/WEB-INF/web.xml
build/build.xml
src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java [new file with mode: 0644]
src/com/vaadin/tests/appengine/GAESyncTest.java [new file with mode: 0644]
src/com/vaadin/tests/appengine/img1.png [new file with mode: 0644]

index e1a96e74135c52998ed75af2fcaa56e927b80678..652179b7dc1ccb2ae9aa7ad19c505c477eecab90 100644 (file)
                        <param-name>application</param-name>
                        <param-value>com.vaadin.tests.book.ChatApplication</param-value>
                </init-param>
+       </servlet>\r
+       \r
+       <servlet>\r
+               <servlet-name>GAESyncTest</servlet-name>\r
+               <servlet-class>com.vaadin.terminal.gwt.server.GAEApplicationServlet</servlet-class>\r
+               <init-param>\r
+                       <param-name>application</param-name>\r
+                       <param-value>com.vaadin.tests.appengine.GAESyncTest</param-value>\r
+               </init-param>\r
        </servlet>
 
        <servlet-mapping>
                <servlet-name>ChatServlet</servlet-name>
                <url-pattern>/chat/*</url-pattern>
                <!-- portlet -->
+       </servlet-mapping>\r
+       \r
+       <servlet-mapping>\r
+               <servlet-name>GAESyncTest</servlet-name>\r
+               <url-pattern>/gaesynctest/*</url-pattern>\r
        </servlet-mapping>
 
        <servlet-mapping>\r
index 2436527241cfb1cc13c71b59d27483c885b28b30..99c8eba8eda7f9fcea885560985059df120bfc2d 100644 (file)
                        <pathelement path="lib/portlet/portal-kernel.jar" />
                        <pathelement path="lib/portlet/portal-service.jar" />
                        <pathelement path="lib/portlet/portlet.jar" />
+                       <pathelement path="lib/appengine/appengine-api.jar" />
                </path>
                <path id="compile.classpath.server-side">
             <path refid="compile.classpath"/>
                        <arg value="${output-dir}/WebContent/VAADIN/widgetsets" />
                        <arg value="com.vaadin.demo.sampler.gwt.SamplerWidgetSet" />
                        <arg value="-style" />
-                       <arg value="OBF" />
+                       <arg value="PRETTY" />
             <jvmarg value="-Xss8M"/>
             <jvmarg value="-Djava.awt.headless=true"/>
                        <classpath>
index 69174587a6cdb629e7d24dd1b6f290c3e3f41815..31ee4dad8118c16f87c7aab23d6d4ac6c4cfbc99 100755 (executable)
@@ -292,14 +292,15 @@ public class ApplicationConnection {
         return (activeRequests > 0);
     }
 
-    private void makeUidlRequest(String requestData, boolean repaintAll,
-            boolean forceSync, boolean analyzeLayouts) {
+    private void makeUidlRequest(final String requestData,
+            final boolean repaintAll, final boolean forceSync,
+            final boolean analyzeLayouts) {
         startRequest();
 
         // Security: double cookie submission pattern
-        requestData = uidl_security_key + VAR_BURST_SEPARATOR + requestData;
+        final String rd = uidl_security_key + VAR_BURST_SEPARATOR + requestData;
 
-        console.log("Making UIDL Request with params: " + requestData);
+        console.log("Making UIDL Request with params: " + rd);
         String uri = getAppUri() + "UIDL" + configuration.getPathInfo();
         if (repaintAll) {
             // collect some client side data that will be sent to server on
@@ -331,13 +332,14 @@ public class ApplicationConnection {
         }
 
         if (!forceSync) {
+            boolean success = false;
             final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST,
                     uri);
             // TODO enable timeout
             // rb.setTimeoutMillis(timeoutMillis);
             rb.setHeader("Content-Type", "text/plain;charset=utf-8");
             try {
-                rb.sendRequest(requestData, new RequestCallback() {
+                rb.sendRequest(rd, new RequestCallback() {
                     public void onError(Request request, Throwable exception) {
                         showCommunicationError(exception.getMessage());
                         endRequest();
@@ -358,6 +360,21 @@ public class ApplicationConnection {
                             showCommunicationError("Invalid status code 0 (server down?)");
                             return;
                             // TODO could add more cases
+                        case 503:
+                            // We'll assume msec instead of the usual seconds
+                            int delay = Integer.parseInt(response
+                                    .getHeader("Retry-After"));
+                            console.log("503, retrying in " + delay + "msec");
+                            (new Timer() {
+                                @Override
+                                public void run() {
+                                    activeRequests--;
+                                    makeUidlRequest(requestData, repaintAll,
+                                            forceSync, analyzeLayouts);
+                                }
+                            }).schedule(delay);
+                            return;
+
                         }
                         if ("init".equals(uidl_security_key)) {
                             // Read security key
@@ -415,7 +432,7 @@ public class ApplicationConnection {
 
             syncSendForce(((HTTPRequestImpl) GWT.create(HTTPRequestImpl.class))
                     .createXmlHTTPRequest(), uri + "&" + PARAM_UNLOADBURST
-                    + "=1", requestData);
+                    + "=1", rd);
         }
     }
 
index 0640477e5c80d47861f23a1a213c759b73016290..af1847a7b7bc1b8b4de307c864ad55516531da71 100644 (file)
@@ -475,12 +475,6 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
                         .endTransaction(application, request);
             }
 
-            // Work-around for GAE session problem. Explicitly touch session so
-            // it is re-serialized.
-            HttpSession session = request.getSession(false);
-            if (session != null) {
-                session.setAttribute("sessionUpdated", new Date().getTime());
-            }
         }
     }
 
@@ -879,7 +873,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
      * @return true if an DownloadStream was sent to the client, false otherwise
      * @throws IOException
      */
-    private boolean handleURI(CommunicationManager applicationManager,
+    protected boolean handleURI(CommunicationManager applicationManager,
             Window window, HttpServletRequest request,
             HttpServletResponse response) throws IOException {
         // Handles the URI
@@ -1085,11 +1079,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
         }
     }
 
-    private enum RequestType {
+    enum RequestType {
         FILE_UPLOAD, UIDL, OTHER;
     }
 
-    private RequestType getRequestType(HttpServletRequest request) {
+    protected RequestType getRequestType(HttpServletRequest request) {
         if (isFileUploadRequest(request)) {
             return RequestType.FILE_UPLOAD;
         } else if (isUIDLRequest(request)) {
@@ -1812,8 +1806,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet {
         String path = getRequestPathInfo(request);
 
         // Main window as the URI is empty
-        if (!(path == null || path.length() == 0 || path.equals("/") || path
-                .startsWith("/APP/"))) {
+        if (!(path == null || path.length() == 0 || path.equals("/"))) {
+            if (path.startsWith("/APP/")) {
+                // Use main window for application resources
+                return application.getMainWindow();
+            }
             String windowName = null;
             if (path.charAt(0) == '/') {
                 path = path.substring(1);
diff --git a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
new file mode 100644 (file)
index 0000000..13d01cf
--- /dev/null
@@ -0,0 +1,96 @@
+package com.vaadin.terminal.gwt.server;
+
+import java.io.IOException;
+import java.util.Date;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+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;
+
+public class GAEApplicationServlet extends ApplicationServlet {
+
+    private static final long serialVersionUID = 2179597952818898526L;
+
+    private static final String MUTEX_BASE = "vaadin.gae.mutex.";
+    // 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;
+
+    @Override
+    protected void service(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException, IOException {
+
+        boolean locked = false;
+        MemcacheService memcache = null;
+        String mutex = null;
+        try {
+            RequestType requestType = getRequestType(request);
+            if (requestType == RequestType.UIDL) {
+                memcache = MemcacheServiceFactory.getMemcacheService();
+                mutex = MUTEX_BASE + request.getSession().getId();
+                // try to get lock
+                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;
+                }
+
+            }
+
+            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());
+                }
+            }
+
+        } catch (DeadlineExceededException e) {
+            System.err.println("DeadlineExceeded for "
+                    + request.getSession().getId());
+            // TODO i18n?
+            criticalNotification(
+                    request,
+                    response,
+                    "Deadline Exceeded",
+                    "I'm sorry, but the operation took too long to complete. We'll try reloading to see where we're at, please take note of any unsaved data...",
+                    "", null);
+        } finally {
+            // "Next, please!"
+            if (locked) {
+                memcache.delete(mutex, KEEP_MUTEX_MILLISECONDS);
+            }
+
+        }
+    }
+
+    protected boolean handleURI(CommunicationManager applicationManager,
+            Window window, HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+
+        if (super.handleURI(applicationManager, window, request, response)) {
+            request.setAttribute("noSerialize", new Object());
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/vaadin/tests/appengine/GAESyncTest.java b/src/com/vaadin/tests/appengine/GAESyncTest.java
new file mode 100644 (file)
index 0000000..0f5c85b
--- /dev/null
@@ -0,0 +1,152 @@
+package com.vaadin.tests.appengine;
+
+import com.google.apphosting.api.DeadlineExceededException;
+import com.vaadin.Application;
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.terminal.ClassResource;
+import com.vaadin.terminal.DownloadStream;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Embedded;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.Window;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Window.Notification;
+
+public class GAESyncTest extends Application {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = -3724319151122707926l;
+
+    @Override
+    public void init() {
+        setMainWindow(new IntrWindow(this));
+
+    }
+
+    @Override
+    public void terminalError(com.vaadin.terminal.Terminal.ErrorEvent event) {
+        Throwable t = event.getThrowable();
+        // Was this caused by a GAE timeout?
+        while (t != null) {
+            if (t instanceof DeadlineExceededException) {
+                getMainWindow().showNotification("Bugger!",
+                        "Deadline Exceeded", Notification.TYPE_ERROR_MESSAGE);
+                return;
+            }
+            t = t.getCause();
+        }
+
+        super.terminalError(event);
+
+    }
+
+    private class IntrWindow extends Window {
+        private int n = 0;
+        private static final long serialVersionUID = -6521351715072191625l;
+        TextField tf;
+        Label l;
+        Application app;
+        GridLayout gl;
+
+        private IntrWindow(Application app) {
+
+            this.app = app;
+            tf = new TextField("Echo thingie");
+            tf.setImmediate(true);
+            tf.addListener(new Property.ValueChangeListener() {
+                public void valueChange(ValueChangeEvent event) {
+                    IntrWindow.this.showNotification((String) event
+                            .getProperty().getValue());
+
+                }
+
+            });
+            addComponent(tf);
+
+            l = new Label("" + n);
+            addComponent(l);
+
+            {
+                Button b = new Button("Slow", new Button.ClickListener() {
+                    public void buttonClick(ClickEvent event) {
+                        try {
+                            Thread.sleep((40000));
+                        } catch (InterruptedException e) {
+                            // TODO Auto-generated catch block
+                            e.printStackTrace();
+                        }
+                    }
+
+                });
+                addComponent(b);
+            }
+
+            {
+                Button b = new Button("Add", new Button.ClickListener() {
+
+                    public void buttonClick(ClickEvent event) {
+                        if (getWindow() == getApplication().getMainWindow()) {
+                            getWindow().showNotification("main");
+                            try {
+                                Thread.sleep((5000));
+                            } catch (InterruptedException e) {
+                                // TODO Auto-generated catch block
+                                e.printStackTrace();
+                            }
+                        }
+                        addImage();
+                    }
+
+                });
+                addComponent(b);
+            }
+
+            gl = new GridLayout(30, 50);
+            addComponent(gl);
+
+        }
+
+        private void addImage() {
+            ClassResource res = new ClassResource("img1.png", app) {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public DownloadStream getStream() {
+                    try {
+                        Thread.sleep((long) (Math.random() * 5000));
+                    } catch (InterruptedException e) {
+                        // TODO Auto-generated catch block
+                        e.printStackTrace();
+                    }
+                    return super.getStream();
+                }
+
+            };
+            res.setCacheTime(0);
+            Embedded emb = new Embedded("" + n, res);
+            emb.setWidth("30px");
+            emb.setHeight("5px");
+            gl.addComponent(emb);
+            l.setValue("" + n++);
+        }
+
+    }
+
+    @Override
+    public Window getWindow(String name) {
+        Window w = super.getWindow(name);
+        if (w == null) {
+            w = new IntrWindow(this);
+            addWindow(w);
+        }
+        return w;
+
+    }
+
+}
diff --git a/src/com/vaadin/tests/appengine/img1.png b/src/com/vaadin/tests/appengine/img1.png
new file mode 100644 (file)
index 0000000..d249324
Binary files /dev/null and b/src/com/vaadin/tests/appengine/img1.png differ