rootConnector.init(cnf.getRootPanelId(), this); | rootConnector.init(cnf.getRootPanelId(), this); | ||||
showLoadingIndicator(); | showLoadingIndicator(); | ||||
scheduleHeartbeat(); | |||||
} | } | ||||
/** | /** | ||||
public SerializerMap getSerializerMap() { | public SerializerMap getSerializerMap() { | ||||
return serializerMap; | 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); | |||||
} | |||||
} | |||||
} | } |
import com.google.gwt.dom.client.NativeEvent; | import com.google.gwt.dom.client.NativeEvent; | ||||
import com.google.gwt.dom.client.Style; | import com.google.gwt.dom.client.Style; | ||||
import com.google.gwt.dom.client.Style.Position; | 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.Command; | ||||
import com.google.gwt.user.client.DOM; | import com.google.gwt.user.client.DOM; | ||||
import com.google.gwt.user.client.Event; | import com.google.gwt.user.client.Event; | ||||
import com.google.gwt.user.client.History; | 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.Window; | ||||
import com.google.gwt.user.client.ui.RootPanel; | import com.google.gwt.user.client.ui.RootPanel; | ||||
import com.google.gwt.user.client.ui.Widget; | import com.google.gwt.user.client.ui.Widget; | ||||
com.google.gwt.user.client.Window.setTitle(title); | 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 | @Override | ||||
} | } | ||||
}); | }); | ||||
} | } | ||||
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) { | |||||
} | |||||
} | |||||
} | } |
} | } | ||||
protected enum RequestType { | 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) { | protected RequestType getRequestType(WrappedPortletRequest wrappedRequest) { | ||||
} else if (ServletPortletHelper | } else if (ServletPortletHelper | ||||
.isApplicationResourceRequest(wrappedRequest)) { | .isApplicationResourceRequest(wrappedRequest)) { | ||||
return RequestType.APPLICATION_RESOURCE; | return RequestType.APPLICATION_RESOURCE; | ||||
} else if (ServletPortletHelper.isHeartbeatRequest(wrappedRequest)) { | |||||
return RequestType.HEARTBEAT; | |||||
} else if (isDummyRequest(resourceRequest)) { | } else if (isDummyRequest(resourceRequest)) { | ||||
return RequestType.DUMMY; | return RequestType.DUMMY; | ||||
} else { | } else { | ||||
Application application = null; | Application application = null; | ||||
boolean transactionStarted = false; | boolean transactionStarted = false; | ||||
boolean requestStarted = false; | boolean requestStarted = false; | ||||
boolean applicationRunning = false; | |||||
try { | try { | ||||
// TODO What about PARAM_UNLOADBURST & redirectToApplication?? | // TODO What about PARAM_UNLOADBURST & redirectToApplication?? | ||||
applicationManager.serveConnectorResource(wrappedRequest, | applicationManager.serveConnectorResource(wrappedRequest, | ||||
wrappedResponse); | wrappedResponse); | ||||
return; | return; | ||||
} else if (requestType == RequestType.HEARTBEAT) { | |||||
applicationManager.handleHeartbeatRequest(wrappedRequest, | |||||
wrappedResponse, application); | |||||
return; | |||||
} | } | ||||
/* Update browser information from request */ | /* Update browser information from request */ | ||||
/* Start the newly created application */ | /* Start the newly created application */ | ||||
startApplication(request, application, applicationContext); | startApplication(request, application, applicationContext); | ||||
applicationRunning = true; | |||||
/* | /* | ||||
* Transaction starts. Call transaction listeners. Transaction | * Transaction starts. Call transaction listeners. Transaction | ||||
handleServiceException(wrappedRequest, wrappedResponse, | handleServiceException(wrappedRequest, wrappedResponse, | ||||
application, e); | application, e); | ||||
} finally { | } finally { | ||||
if (applicationRunning) { | |||||
application.closeInactiveRoots(); | |||||
} | |||||
// Notifies transaction end | // Notifies transaction end | ||||
try { | try { | ||||
if (transactionStarted) { | if (transactionStarted) { |
Application application = null; | Application application = null; | ||||
boolean transactionStarted = false; | boolean transactionStarted = false; | ||||
boolean requestStarted = false; | boolean requestStarted = false; | ||||
boolean applicationRunning = false; | |||||
try { | try { | ||||
// If a duplicate "close application" URL is received for an | // If a duplicate "close application" URL is received for an | ||||
if (requestType == RequestType.CONNECTOR_RESOURCE) { | if (requestType == RequestType.CONNECTOR_RESOURCE) { | ||||
applicationManager.serveConnectorResource(request, response); | applicationManager.serveConnectorResource(request, response); | ||||
return; | return; | ||||
} else if (requestType == RequestType.HEARTBEAT) { | |||||
applicationManager.handleHeartbeatRequest(request, response, | |||||
application); | |||||
return; | |||||
} | } | ||||
/* Update browser information from the request */ | /* Update browser information from the request */ | ||||
// Start the application if it's newly created | // Start the application if it's newly created | ||||
startApplication(request, application, webApplicationContext); | startApplication(request, application, webApplicationContext); | ||||
applicationRunning = true; | |||||
/* | /* | ||||
* Transaction starts. Call transaction listeners. Transaction end | * Transaction starts. Call transaction listeners. Transaction end | ||||
} catch (final Throwable e) { | } catch (final Throwable e) { | ||||
handleServiceException(request, response, application, e); | handleServiceException(request, response, application, e); | ||||
} finally { | } finally { | ||||
if (applicationRunning) { | |||||
application.closeInactiveRoots(); | |||||
} | |||||
// Notifies transaction end | // Notifies transaction end | ||||
try { | try { | ||||
if (transactionStarted) { | if (transactionStarted) { | ||||
} | } | ||||
protected enum RequestType { | 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) { | protected RequestType getRequestType(WrappedHttpServletRequest request) { | ||||
return RequestType.STATIC_FILE; | return RequestType.STATIC_FILE; | ||||
} else if (ServletPortletHelper.isApplicationResourceRequest(request)) { | } else if (ServletPortletHelper.isApplicationResourceRequest(request)) { | ||||
return RequestType.APPLICATION_RESOURCE; | return RequestType.APPLICATION_RESOURCE; | ||||
} else if (ServletPortletHelper.isHeartbeatRequest(request)) { | |||||
return RequestType.HEARTBEAT; | |||||
} | } | ||||
return RequestType.OTHER; | return RequestType.OTHER; | ||||
import com.vaadin.terminal.VariableOwner; | import com.vaadin.terminal.VariableOwner; | ||||
import com.vaadin.terminal.WrappedRequest; | import com.vaadin.terminal.WrappedRequest; | ||||
import com.vaadin.terminal.WrappedResponse; | 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.BootstrapHandler.BootstrapContext; | ||||
import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; | import com.vaadin.terminal.gwt.server.ComponentSizeValidator.InvalidLayout; | ||||
import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; | import com.vaadin.terminal.gwt.server.RpcManager.RpcInvocationException; | ||||
* This is a common base class for the server-side implementations of the | * This is a common base class for the server-side implementations of the | ||||
* communication system between the client code (compiled with GWT into | * communication system between the client code (compiled with GWT into | ||||
* JavaScript) and the server side components. Its client side counterpart is | * JavaScript) and the server side components. Its client side counterpart is | ||||
* {@link ApplicationConstants}. | |||||
* {@link ApplicationConnection}. | |||||
* | * | ||||
* TODO Document better! | * TODO Document better! | ||||
*/ | */ | ||||
return; | return; | ||||
} | } | ||||
// Keep the root alive | |||||
root.heartbeat(); | |||||
// Change all variables based on request parameters | // Change all variables based on request parameters | ||||
if (!handleVariables(request, response, callback, application, root)) { | if (!handleVariables(request, response, callback, application, root)) { | ||||
} | } | ||||
/** | |||||
* 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, | public StreamVariable getStreamVariable(String connectorId, | ||||
String variableName) { | String variableName) { | ||||
Map<String, StreamVariable> map = pidToNameToStreamVariable | Map<String, StreamVariable> map = pidToNameToStreamVariable |
import com.vaadin.ui.Root; | 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 | * 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 | * use this file except in compliance with the License. You may obtain a copy of | ||||
return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH); | return hasPathPrefix(request, ApplicationConstants.APP_REQUEST_PATH); | ||||
} | } | ||||
public static boolean isHeartbeatRequest(WrappedRequest request) { | |||||
return hasPathPrefix(request, | |||||
ApplicationConstants.HEARTBEAT_REQUEST_PATH); | |||||
} | |||||
} | } |
public static final String UIDL_REQUEST_PATH = "UIDL/"; | 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 | public static final String CONNECTOR_RESOURCE_PREFIX = APP_REQUEST_PATH | ||||
+ "CONNECTOR"; | + "CONNECTOR"; | ||||