summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMarc Englund <marc.englund@itmill.com>2009-09-04 12:38:51 +0000
committerMarc Englund <marc.englund@itmill.com>2009-09-04 12:38:51 +0000
commita7aca3444a898e26a417fab1f99f122e544be0f9 (patch)
tree19b9f6eedbfcf3c562bcd62c811162fb478cd667 /src
parent108ad9bf8cf1350186cbfe0370a29cd5870352c3 (diff)
downloadvaadin-framework-a7aca3444a898e26a417fab1f99f122e544be0f9.tar.gz
vaadin-framework-a7aca3444a898e26a417fab1f99f122e544be0f9.zip
Google appengine support w/ own 'session handling' and synchronization. For #3058 and #2835
svn changeset:8669/svn branch:6.1
Diffstat (limited to 'src')
-rw-r--r--src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java55
-rw-r--r--src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java205
-rw-r--r--src/com/vaadin/tests/appengine/GAESyncTest.java2
3 files changed, 214 insertions, 48 deletions
diff --git a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
index af1847a7b7..31d5d5ac96 100644
--- a/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
+++ b/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
@@ -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();
}
diff --git a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
index 13d01cfb49..e24f405ac1 100644
--- a/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
+++ b/src/com/vaadin/terminal/gwt/server/GAEApplicationServlet.java
@@ -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;
}
}
diff --git a/src/com/vaadin/tests/appengine/GAESyncTest.java b/src/com/vaadin/tests/appengine/GAESyncTest.java
index 0f5c85b239..a28da32c39 100644
--- a/src/com/vaadin/tests/appengine/GAESyncTest.java
+++ b/src/com/vaadin/tests/appengine/GAESyncTest.java
@@ -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();