@@ -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); | |||
} | |||
} | |||
} |
@@ -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) { | |||
} | |||
} | |||
} |
@@ -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) { |
@@ -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; | |||
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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"; | |||