aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java70
-rw-r--r--client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java39
-rw-r--r--server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java15
-rw-r--r--server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java15
-rw-r--r--server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java38
-rw-r--r--server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java7
-rw-r--r--shared/src/com/vaadin/shared/ApplicationConstants.java2
7 files changed, 143 insertions, 43 deletions
diff --git a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
index a8852fe9fa..d2deb70190 100644
--- a/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java
@@ -230,6 +230,8 @@ public class ApplicationConnection {
rootConnector.init(cnf.getRootPanelId(), this);
showLoadingIndicator();
+
+ scheduleHeartbeat();
}
/**
@@ -2520,4 +2522,72 @@ public class ApplicationConnection {
public SerializerMap getSerializerMap() {
return serializerMap;
}
+
+ /**
+ * Schedules a heartbeat request.
+ *
+ * @see #sendHeartbeat()
+ */
+ private void scheduleHeartbeat() {
+ final int interval = 1000 * getConfiguration().getHeartbeatInterval();
+ if (interval > 0) {
+ new Timer() {
+ @Override
+ public void run() {
+ sendHeartbeat();
+ }
+ }.schedule(interval);
+ }
+ }
+
+ /**
+ * Sends a heartbeat request to the server.
+ * <p>
+ * Heartbeat requests are used to inform the server that the client-side is
+ * still alive. If the client page is closed or the connection lost, the
+ * server will eventually close the inactive Root.
+ * <p>
+ * <b>TODO</b>: Improved error handling, like in doUidlRequest().
+ *
+ * @see #scheduleHeartbeat()
+ * @see com.vaadin.ui.Root#heartbeat()
+ */
+ private void sendHeartbeat() {
+ final String uri = addGetParameters(
+ translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ApplicationConstants.HEARTBEAT_REQUEST_PATH),
+ ApplicationConstants.ROOT_ID_PARAMETER + "="
+ + getConfiguration().getRootId());
+
+ final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
+
+ final RequestCallback callback = new RequestCallback() {
+
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ int status = response.getStatusCode();
+ if (status == Response.SC_OK) {
+ // TODO Permit retry in some error situations
+ scheduleHeartbeat();
+ } else {
+ VConsole.error("Heartbeat request failed with status code "
+ + status);
+ }
+ }
+
+ @Override
+ public void onError(Request request, Throwable exception) {
+ VConsole.error("Heartbeat request resulted in exception");
+ VConsole.error(exception);
+ }
+ };
+
+ rb.setCallback(callback);
+
+ try {
+ rb.send();
+ } catch (RequestException re) {
+ callback.onError(null, re);
+ }
+ }
}
diff --git a/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java
index 0a0e0e5082..39702e6ba0 100644
--- a/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java
+++ b/client/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java
@@ -23,16 +23,10 @@ import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.http.client.Request;
-import com.google.gwt.http.client.RequestBuilder;
-import com.google.gwt.http.client.RequestCallback;
-import com.google.gwt.http.client.RequestException;
-import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
-import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -90,14 +84,6 @@ public class RootConnector extends AbstractComponentContainerConnector
com.google.gwt.user.client.Window.setTitle(title);
}
});
- final int heartbeatInterval = getState().getHeartbeatInterval();
- new Timer() {
- @Override
- public void run() {
- sendHeartbeat();
- schedule(heartbeatInterval);
- }
- }.schedule(heartbeatInterval);
}
@Override
@@ -455,29 +441,4 @@ public class RootConnector extends AbstractComponentContainerConnector
}
});
}
-
- private void sendHeartbeat() {
- RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, "url");
-
- rb.setCallback(new RequestCallback() {
-
- @Override
- public void onResponseReceived(Request request, Response response) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void onError(Request request, Throwable exception) {
- // TODO Auto-generated method stub
-
- }
- });
-
- try {
- rb.send();
- } catch (RequestException re) {
-
- }
- }
}
diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
index bd39504237..dea42b6b69 100644
--- a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
+++ b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationPortlet.java
@@ -340,7 +340,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
}
protected enum RequestType {
- FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE;
+ FILE_UPLOAD, UIDL, RENDER, STATIC_FILE, APPLICATION_RESOURCE, DUMMY, EVENT, ACTION, UNKNOWN, BROWSER_DETAILS, CONNECTOR_RESOURCE, HEARTBEAT;
}
protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) {
@@ -361,6 +361,8 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
} else if (ServletPortletHelper
.isApplicationResourceRequest(wrappedRequest)) {
return RequestType.APPLICATION_RESOURCE;
+ } else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) {
+ return RequestType.HEARTBEAT;
} else if (isDummyRequest(resourceRequest)) {
return RequestType.DUMMY;
} else {
@@ -431,6 +433,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
Application application = null;
boolean transactionStarted = false;
boolean requestStarted = false;
+ boolean applicationRunning = false;
try {
// TODO What about PARAM_UNLOADBURST & redirectToApplication??
@@ -459,6 +462,10 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
applicationManager.serveConnectorResource(wrappedRequest,
wrappedResponse);
return;
+ } else if (requestType == RequestType.HEARTBEAT) {
+ applicationManager.handleHeartbeatRequest(wrappedRequest,
+ wrappedResponse, application);
+ return;
}
/* Update browser information from request */
@@ -477,6 +484,7 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
/* Start the newly created application */
startApplication(request, application, applicationContext);
+ applicationRunning = true;
/*
* Transaction starts. Call transaction listeners. Transaction
@@ -585,6 +593,11 @@ public abstract class AbstractApplicationPortlet extends GenericPortlet
handleServiceException(wrappedRequest, wrappedResponse,
application, e);
} finally {
+
+ if (applicationRunning) {
+ application.closeInactiveRoots();
+ }
+
// Notifies transaction end
try {
if (transactionStarted) {
diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
index 062ba6cdf7..0fc8bc09a0 100644
--- a/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
+++ b/server/src/com/vaadin/terminal/gwt/server/AbstractApplicationServlet.java
@@ -248,6 +248,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
Application application = null;
boolean transactionStarted = false;
boolean requestStarted = false;
+ boolean applicationRunning = false;
try {
// If a duplicate "close application" URL is received for an
@@ -287,6 +288,10 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
if (requestType == RequestType.CONNECTOR_RESOURCE) {
applicationManager.serveConnectorResource(request, response);
return;
+ } else if (requestType == RequestType.HEARTBEAT) {
+ applicationManager.handleHeartbeatRequest(request, response,
+ application);
+ return;
}
/* Update browser information from the request */
@@ -304,6 +309,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
// Start the application if it's newly created
startApplication(request, application, webApplicationContext);
+ applicationRunning = true;
/*
* Transaction starts. Call transaction listeners. Transaction end
@@ -354,6 +360,11 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
} catch (final Throwable e) {
handleServiceException(request, response, application, e);
} finally {
+
+ if (applicationRunning) {
+ application.closeInactiveRoots();
+ }
+
// Notifies transaction end
try {
if (transactionStarted) {
@@ -1121,7 +1132,7 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
}
protected enum RequestType {
- FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE;
+ FILE_UPLOAD, BROWSER_DETAILS, UIDL, OTHER, STATIC_FILE, APPLICATION_RESOURCE, CONNECTOR_RESOURCE, HEARTBEAT;
}
protected RequestType getRequestType(WrappedHttpServletRequest request) {
@@ -1137,6 +1148,8 @@ public abstract class AbstractApplicationServlet extends HttpServlet implements
return RequestType.STATIC_FILE;
} else if (ServletPortletHelper.isApplicationResourceRequest(request)) {
return RequestType.APPLICATION_RESOURCE;
+ } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
+ return RequestType.HEARTBEAT;
}
return RequestType.OTHER;
diff --git a/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java b/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
index 00e65382cd..3099903454 100644
--- a/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
+++ b/server/src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java
@@ -87,6 +87,7 @@ import com.vaadin.terminal.Vaadin6Component;
import com.vaadin.terminal.VariableOwner;
import com.vaadin.terminal.WrappedRequest;
import com.vaadin.terminal.WrappedResponse;
+import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.server.BootstrapHandler.BootstrapContext;
import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout;
import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException;
@@ -102,7 +103,7 @@ import com.vaadin.ui.Window;
* This is a common base class for the server-side implementations of the
* communication system between the client code (compiled with GWT into
* JavaScript) and the server side components. Its client side counterpart is
- * {@link ApplicationConstants}.
+ * {@link ApplicationConnection}.
*
* TODO Document better!
*/
@@ -577,6 +578,9 @@ public abstract class AbstractCommunicationManager implements Serializable {
return;
}
+ // Keep the root alive
+ root.heartbeat();
+
// Change all variables based on request parameters
if (!handleVariables(request, response, callback, application, root)) {
@@ -2634,6 +2638,38 @@ public abstract class AbstractCommunicationManager implements Serializable {
}
+ /**
+ * Handles a heartbeat request. Heartbeat requests are periodically sent by
+ * the client-side to inform the server that the root sending the heartbeat
+ * is still alive (the browser window is open, the connection is up) even
+ * when there are no UIDL requests for a prolonged period of time. Roots
+ * that do not receive either heartbeat or UIDL requests are eventually
+ * removed from the application and garbage collected.
+ *
+ * @param request
+ * @param response
+ * @param application
+ * @throws IOException
+ */
+ public void handleHeartbeatRequest(WrappedRequest request,
+ WrappedResponse response, Application application)
+ throws IOException {
+ Root root = null;
+ try {
+ int rootId = Integer.parseInt(request
+ .getParameter(ApplicationConstants.ROOT_ID_PARAMETER));
+ root = application.getRootById(rootId);
+ } catch (NumberFormatException nfe) {
+ // null-check below handles this as well
+ }
+ if (root != null) {
+ root.heartbeat();
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND,
+ "Root not found");
+ }
+ }
+
public StreamVariable getStreamVariable(String connectorId,
String variableName) {
Map<String, StreamVariable> map = pidToNameToStreamVariable
diff --git a/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java b/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java
index 200f9a9103..6911101920 100644
--- a/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java
+++ b/server/src/com/vaadin/terminal/gwt/server/ServletPortletHelper.java
@@ -9,7 +9,7 @@ import com.vaadin.terminal.WrappedRequest;
import com.vaadin.ui.Root;
/*
- * Copyright 2011 Vaadin Ltd.
+ * Copyright 2011 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
@@ -129,4 +129,9 @@ class ServletPortletHelper implements Serializable {
return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH);
}
+ public static boolean isHeartbeatRequest(WrappedRequest request) {
+ return hasPathPrefix(request,
+ ApplicationConstants.HEARTBEAT_REQUEST_PATH);
+ }
+
}
diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java
index 31e633a210..9d1ed7341d 100644
--- a/shared/src/com/vaadin/shared/ApplicationConstants.java
+++ b/shared/src/com/vaadin/shared/ApplicationConstants.java
@@ -23,6 +23,8 @@ public class ApplicationConstants {
public static final String UIDL_REQUEST_PATH = "UIDL/";
+ public static final String HEARTBEAT_REQUEST_PATH = "HEARTBEAT/";
+
public static final String CONNECTOR_RESOURCE_PREFIX = APP_REQUEST_PATH
+ "CONNECTOR";