summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WebContent/WEB-INF/web.xml14
-rw-r--r--build/build.xml3
-rwxr-xr-xsrc/com/vaadin/terminal/gwt/client/ApplicationConnection.java29
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java19
-rw-r--r--src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java96
-rw-r--r--src/com/vaadin/tests/appengine/GAESyncTest.java152
-rw-r--r--src/com/vaadin/tests/appengine/img1.pngbin0 -> 7433 bytes
7 files changed, 295 insertions, 18 deletions
diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml
index e1a96e7413..652179b7dc 100644
--- a/WebContent/WEB-INF/web.xml
+++ b/WebContent/WEB-INF/web.xml
@@ -350,6 +350,15 @@
<param-name>application</param-name>
<param-value>com.vaadin.tests.book.ChatApplication</param-value>
</init-param>
+ </servlet>
+
+ <servlet>
+ <servlet-name>GAESyncTest</servlet-name>
+ <servlet-class>com.vaadin.terminal.gwt.server.GAEApplicationServlet</servlet-class>
+ <init-param>
+ <param-name>application</param-name>
+ <param-value>com.vaadin.tests.appengine.GAESyncTest</param-value>
+ </init-param>
</servlet>
<servlet-mapping>
@@ -503,6 +512,11 @@
<servlet-name>ChatServlet</servlet-name>
<url-pattern>/chat/*</url-pattern>
<!-- portlet -->
+ </servlet-mapping>
+
+ <servlet-mapping>
+ <servlet-name>GAESyncTest</servlet-name>
+ <url-pattern>/gaesynctest/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
diff --git a/build/build.xml b/build/build.xml
index 2436527241..99c8eba8ed 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -239,6 +239,7 @@
<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"/>
@@ -854,7 +855,7 @@
<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>
diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
index 69174587a6..31ee4dad81 100755
--- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
+++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
@@ -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);
}
}
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
index 0640477e5c..af1847a7b7 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
@@ -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
index 0000000000..13d01cfb49
--- /dev/null
+++ b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
@@ -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
index 0000000000..0f5c85b239
--- /dev/null
+++ b/src/com/vaadin/tests/appengine/GAESyncTest.java
@@ -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
index 0000000000..d249324e0c
--- /dev/null
+++ b/src/com/vaadin/tests/appengine/img1.png
Binary files differ