Change-Id: Id148ac8a5b86a76ed966f96ea7732c35ad0d056dtags/7.6.0.alpha5
@@ -17,6 +17,7 @@ $v-line-height: $line-height !default; | |||
@import "caption/caption.scss"; | |||
@import "colorpicker/colorpicker.scss"; | |||
@import "common/common.scss"; | |||
@import "common/reconnect-dialog.scss"; | |||
@import "csslayout/csslayout.scss"; | |||
@import "customcomponent/customcomponent.scss"; | |||
@import "customlayout/customlayout.scss"; | |||
@@ -89,6 +90,7 @@ $v-line-height: $line-height !default; | |||
// here for now to preserve old semantics | |||
@include base-common; | |||
@include base-reconnect-dialog; | |||
@include base-layout; | |||
@include base-csslayout; |
@@ -0,0 +1,32 @@ | |||
@mixin base-reconnect-dialog { | |||
.v-reconnect-dialog { | |||
color: white; | |||
top: 12px; | |||
right: 12px; | |||
max-width: 100%; | |||
border-radius: 0; | |||
@include box-shadow(0 0 20px 0 rgba(0,0,0,0.25)); | |||
padding: 10px; | |||
background-color: #444; | |||
text-align: center; | |||
.text { | |||
display: inline-block; | |||
padding-left: 10px; | |||
} | |||
.spinner { | |||
background-image: url(img/reconnect-spinner.gif); | |||
width: 31px; | |||
height: 31px; | |||
display: inline-block; | |||
visibility: hidden; | |||
vertical-align: middle; | |||
} | |||
&.active .spinner { | |||
visibility: visible; | |||
} | |||
} | |||
} |
@@ -2,6 +2,7 @@ | |||
@import "contextmenu"; | |||
@import "overlay"; | |||
@import "tooltip"; | |||
@import "reconnect-dialog"; | |||
/* | |||
@@ -374,6 +375,7 @@ $valo-shared-pathPrefix: null; | |||
@include valo-contextmenu; | |||
@include valo-reconnect-dialog; | |||
} | |||
@@ -0,0 +1,31 @@ | |||
@mixin valo-reconnect-dialog { | |||
.v-reconnect-dialog { | |||
color: white; | |||
top: $v-layout-spacing-vertical; | |||
right: $v-layout-spacing-horizontal; | |||
max-width: 100%; | |||
border-radius: 0; | |||
@include box-shadow(0 0 20px 0 rgba(0,0,0,0.25)); | |||
padding: round($v-unit-size/3) round($v-unit-size/2.5); | |||
background-color: #444; | |||
background-color: rgba(#444, .9); | |||
line-height: round($v-font-size * 1.4); | |||
text-align: center; | |||
.text { | |||
display: inline-block; | |||
padding-left: 10px; | |||
} | |||
.spinner { | |||
@include valo-spinner; | |||
display: none; | |||
vertical-align: middle; | |||
} | |||
&.active .spinner { | |||
display: inline-block; | |||
} | |||
} | |||
} |
@@ -96,6 +96,19 @@ | |||
</init-param> | |||
<async-supported>true</async-supported> | |||
</servlet> | |||
<servlet> | |||
<servlet-name>CommErrorEmulator</servlet-name> | |||
<servlet-class>com.vaadin.tests.application.CommErrorEmulatorServlet</servlet-class> | |||
<init-param> | |||
<param-name>heartbeatInterval</param-name> | |||
<param-value>10</param-value> | |||
</init-param> | |||
<init-param> | |||
<param-name>ui</param-name> | |||
<param-value>com.vaadin.tests.application.CommErrorEmulatorUI</param-value> | |||
</init-param> | |||
<async-supported>true</async-supported> | |||
</servlet> | |||
<servlet> | |||
<!-- This servlet is a separate instance for the sole purpose of | |||
@@ -184,6 +197,11 @@ | |||
<url-pattern>/integration/*</url-pattern> | |||
</servlet-mapping> | |||
<servlet-mapping> | |||
<servlet-name>CommErrorEmulator</servlet-name> | |||
<url-pattern>/commerror/*</url-pattern> | |||
</servlet-mapping> | |||
<servlet-mapping> | |||
<servlet-name>VaadinStaticFiles</servlet-name> | |||
<url-pattern>/VAADIN/*</url-pattern> |
@@ -26,6 +26,10 @@ | |||
class="com.vaadin.client.metadata.ConnectorBundleLoader" /> | |||
</generate-with> | |||
<replace-with | |||
class="com.vaadin.client.communication.DefaultReconnectDialog"> | |||
<when-type-is class="com.vaadin.client.communication.ReconnectDialog" /> | |||
</replace-with> | |||
<!-- Since 7.2. Compile all permutations (browser support) into one Javascript | |||
file. Speeds up compilation and does not make the Javascript significantly |
@@ -606,7 +606,7 @@ public class ApplicationConfiguration implements EntryPoint { | |||
* | |||
* @param c | |||
*/ | |||
static void runWhenDependenciesLoaded(Command c) { | |||
public static void runWhenDependenciesLoaded(Command c) { | |||
if (dependenciesLoading == 0) { | |||
c.execute(); | |||
} else { |
@@ -28,6 +28,7 @@ import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.core.client.JsArray; | |||
import com.google.gwt.dom.client.Element; | |||
import com.vaadin.client.communication.JavaScriptMethodInvocation; | |||
import com.vaadin.client.communication.ServerRpcQueue; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; | |||
import com.vaadin.client.ui.layout.ElementResizeEvent; | |||
@@ -63,7 +64,7 @@ public class JavaScriptConnectorHelper { | |||
/** | |||
* The id of the previous response for which state changes have been | |||
* processed. If this is the same as the | |||
* {@link ApplicationConnection#getLastResponseId()}, it means that the | |||
* {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that the | |||
* state change has already been handled and should not be done again. | |||
*/ | |||
private int processedResponseId = -1; | |||
@@ -92,7 +93,7 @@ public class JavaScriptConnectorHelper { | |||
} | |||
private void processStateChanges() { | |||
int lastResponseId = connector.getConnection().getLastResponseId(); | |||
int lastResponseId = connector.getConnection().getLastSeenServerSyncId(); | |||
if (processedResponseId == lastResponseId) { | |||
return; | |||
} | |||
@@ -357,9 +358,10 @@ public class JavaScriptConnectorHelper { | |||
for (int i = 0; i < parameters.length; i++) { | |||
parameters[i] = argumentsArray.get(i); | |||
} | |||
connector.getConnection().addMethodInvocationToQueue( | |||
new JavaScriptMethodInvocation(connector.getConnectorId(), | |||
iface, method, parameters), false, false); | |||
ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); | |||
rpcQueue.add(new JavaScriptMethodInvocation(connector.getConnectorId(), | |||
iface, method, parameters), false); | |||
rpcQueue.flush(); | |||
} | |||
private String findWildcardInterface(String method) { | |||
@@ -390,8 +392,9 @@ public class JavaScriptConnectorHelper { | |||
connector.getConnectorId(), | |||
"com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", | |||
new Object[] { name, arguments }); | |||
connector.getConnection().addMethodInvocationToQueue(invocation, false, | |||
false); | |||
ServerRpcQueue rpcQueue = ServerRpcQueue.get(connector.getConnection()); | |||
rpcQueue.add(invocation, false); | |||
rpcQueue.flush(); | |||
} | |||
public void setNativeState(JavaScriptObject state) { |
@@ -70,6 +70,13 @@ public class LayoutManager { | |||
}; | |||
private boolean everythingNeedsMeasure = false; | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the application connection this instance is connected to | |||
*/ | |||
public void setConnection(ApplicationConnection connection) { | |||
if (this.connection != null) { | |||
throw new RuntimeException( | |||
@@ -252,7 +259,7 @@ public class LayoutManager { | |||
"Can't start a new layout phase before the previous layout phase ends."); | |||
} | |||
if (connection.isUpdatingState()) { | |||
if (connection.getMessageHandler().isUpdatingState()) { | |||
// If assertions are enabled, throw an exception | |||
assert false : STATE_CHANGE_MESSAGE; | |||
@@ -1793,7 +1800,7 @@ public class LayoutManager { | |||
/** | |||
* Clean measured sizes which are no longer needed. Only for IE8. | |||
*/ | |||
protected void cleanMeasuredSizes() { | |||
public void cleanMeasuredSizes() { | |||
} | |||
private static Logger getLogger() { |
@@ -67,7 +67,7 @@ public class LayoutManagerIE8 extends LayoutManager { | |||
} | |||
@Override | |||
protected void cleanMeasuredSizes() { | |||
public void cleanMeasuredSizes() { | |||
Profiler.enter("LayoutManager.cleanMeasuredSizes"); | |||
// #12688: IE8 was leaking memory when adding&removing components. |
@@ -747,36 +747,46 @@ public class Util { | |||
+ id); | |||
} | |||
for (MethodInvocation invocation : invocations) { | |||
Object[] parameters = invocation.getParameters(); | |||
String formattedParams = null; | |||
if (ApplicationConstants.UPDATE_VARIABLE_METHOD.equals(invocation | |||
.getMethodName()) && parameters.length == 2) { | |||
// name, value | |||
Object value = parameters[1]; | |||
// TODO paintables inside lists/maps get rendered as | |||
// components in the debug console | |||
String formattedValue = value instanceof ServerConnector ? ((ServerConnector) value) | |||
.getConnectorId() : String.valueOf(value); | |||
formattedParams = parameters[0] + " : " + formattedValue; | |||
} | |||
if (null == formattedParams) { | |||
formattedParams = (null != parameters) ? Arrays | |||
.toString(parameters) : null; | |||
} | |||
getLogger().info( | |||
"\t\t" + invocation.getInterfaceName() + "." | |||
+ invocation.getMethodName() + "(" | |||
+ formattedParams + ")"); | |||
getLogger().info("\t\t" + getInvocationDebugString(invocation)); | |||
} | |||
} | |||
/** | |||
* Produces a string representation of a method invocation, suitable for | |||
* debug output | |||
* | |||
* @since 7.5 | |||
* @param invocation | |||
* @return | |||
*/ | |||
private static String getInvocationDebugString(MethodInvocation invocation) { | |||
Object[] parameters = invocation.getParameters(); | |||
String formattedParams = null; | |||
if (ApplicationConstants.UPDATE_VARIABLE_METHOD.equals(invocation | |||
.getMethodName()) && parameters.length == 2) { | |||
// name, value | |||
Object value = parameters[1]; | |||
// TODO paintables inside lists/maps get rendered as | |||
// components in the debug console | |||
String formattedValue = value instanceof ServerConnector ? ((ServerConnector) value) | |||
.getConnectorId() : String.valueOf(value); | |||
formattedParams = parameters[0] + " : " + formattedValue; | |||
} | |||
if (null == formattedParams) { | |||
formattedParams = (null != parameters) ? Arrays | |||
.toString(parameters) : null; | |||
} | |||
return invocation.getInterfaceName() + "." + invocation.getMethodName() | |||
+ "(" + formattedParams + ")"; | |||
} | |||
static void logVariableBurst(ApplicationConnection c, | |||
Collection<MethodInvocation> loggedBurst) { | |||
public static void logMethodInvocations(ApplicationConnection c, | |||
Collection<MethodInvocation> methodInvocations) { | |||
try { | |||
getLogger().info("Variable burst to be sent to server:"); | |||
getLogger().info("RPC invocations to be sent to the server:"); | |||
String curId = null; | |||
ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>(); | |||
for (MethodInvocation methodInvocation : loggedBurst) { | |||
for (MethodInvocation methodInvocation : methodInvocations) { | |||
String id = methodInvocation.getConnectorId(); | |||
if (curId == null) { | |||
@@ -792,7 +802,8 @@ public class Util { | |||
printConnectorInvocations(invocations, curId, c); | |||
} | |||
} catch (Exception e) { | |||
getLogger().log(Level.SEVERE, "Error sending variable burst", e); | |||
getLogger() | |||
.log(Level.SEVERE, "Error logging method invocations", e); | |||
} | |||
} | |||
@@ -108,12 +108,12 @@ public final class ValueMap extends JavaScriptObject { | |||
return this[name]; | |||
}-*/; | |||
native String getAsString(String name) | |||
public native String getAsString(String name) | |||
/*-{ | |||
return '' + this[name]; | |||
}-*/; | |||
native JavaScriptObject getJavaScriptObject(String name) | |||
public native JavaScriptObject getJavaScriptObject(String name) | |||
/*-{ | |||
return this[name]; | |||
}-*/; |
@@ -64,6 +64,23 @@ public class WidgetUtil { | |||
debugger; | |||
}-*/; | |||
/** | |||
* Redirects the browser to the given url or refreshes the page if url is | |||
* null | |||
* | |||
* @since | |||
* @param url | |||
* The url to redirect to or null to refresh | |||
*/ | |||
public static native void redirect(String url) | |||
/*-{ | |||
if (url) { | |||
$wnd.location = url; | |||
} else { | |||
$wnd.location.reload(false); | |||
} | |||
}-*/; | |||
/** | |||
* Helper method for a bug fix #14041. For mozilla getKeyCode return 0 for | |||
* space bar (because space is considered as char). If return 0 use |
@@ -16,7 +16,6 @@ | |||
package com.vaadin.client.communication; | |||
import java.util.ArrayList; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
@@ -27,10 +26,10 @@ import com.vaadin.client.ApplicationConfiguration; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedHandler; | |||
import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; | |||
import com.vaadin.client.ResourceLoader; | |||
import com.vaadin.client.ResourceLoader.ResourceLoadEvent; | |||
import com.vaadin.client.ResourceLoader.ResourceLoadListener; | |||
import com.vaadin.client.ValueMap; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.Version; | |||
import com.vaadin.shared.communication.PushConstants; | |||
@@ -117,8 +116,6 @@ public class AtmospherePushConnection implements PushConnection { | |||
private JavaScriptObject socket; | |||
private ArrayList<JsonObject> messageQueue = new ArrayList<JsonObject>(); | |||
private State state = State.CONNECT_PENDING; | |||
private AtmosphereConfiguration config; | |||
@@ -127,8 +124,6 @@ public class AtmospherePushConnection implements PushConnection { | |||
private String transport; | |||
private CommunicationErrorHandler errorHandler; | |||
/** | |||
* Keeps track of the disconnect confirmation command for cases where | |||
* pending messages should be pushed before actually disconnecting. | |||
@@ -147,10 +142,8 @@ public class AtmospherePushConnection implements PushConnection { | |||
*/ | |||
@Override | |||
public void init(final ApplicationConnection connection, | |||
final PushConfigurationState pushConfiguration, | |||
CommunicationErrorHandler errorHandler) { | |||
final PushConfigurationState pushConfiguration) { | |||
this.connection = connection; | |||
this.errorHandler = errorHandler; | |||
connection.addHandler(ApplicationStoppedEvent.TYPE, | |||
new ApplicationStoppedHandler() { | |||
@@ -201,10 +194,10 @@ public class AtmospherePushConnection implements PushConnection { | |||
String extraParams = UIConstants.UI_ID_PARAMETER + "=" | |||
+ connection.getConfiguration().getUIId(); | |||
if (!connection.getCsrfToken().equals( | |||
ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) { | |||
String csrfToken = connection.getMessageHandler().getCsrfToken(); | |||
if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) { | |||
extraParams += "&" + ApplicationConstants.CSRF_TOKEN_PARAMETER | |||
+ "=" + connection.getCsrfToken(); | |||
+ "=" + csrfToken; | |||
} | |||
// uri is needed to identify the right connection when closing | |||
@@ -225,17 +218,44 @@ public class AtmospherePushConnection implements PushConnection { | |||
} | |||
} | |||
@Override | |||
public boolean isBidirectional() { | |||
if (transport == null) { | |||
return false; | |||
} | |||
if (!transport.equals("websocket")) { | |||
// If we are not using websockets, we want to send XHRs | |||
return false; | |||
} | |||
if (getPushConfigurationState().alwaysUseXhrForServerRequests) { | |||
// If user has forced us to use XHR, let's abide | |||
return false; | |||
} | |||
if (state == State.CONNECT_PENDING) { | |||
// Not sure yet, let's go for using websockets still as still will | |||
// delay the message until a connection is established. When the | |||
// connection is established, bi-directionality will be checked | |||
// again to be sure | |||
} | |||
return true; | |||
}; | |||
private PushConfigurationState getPushConfigurationState() { | |||
return connection.getUIConnector().getState().pushConfiguration; | |||
} | |||
@Override | |||
public void push(JsonObject message) { | |||
switch (state) { | |||
case CONNECT_PENDING: | |||
assert isActive(); | |||
getLogger().info("Queuing push message: " + message.toJson()); | |||
messageQueue.add(message); | |||
break; | |||
case CONNECTED: | |||
assert isActive(); | |||
getLogger().info("Sending push message: " + message.toJson()); | |||
if (!isBidirectional()) { | |||
throw new IllegalStateException( | |||
"This server to client push connection should not be used to send client to server messages"); | |||
} | |||
if (state == State.CONNECTED) { | |||
getLogger().info( | |||
"Sending push (" + transport + ") message to server: " | |||
+ message.toJson()); | |||
if (transport.equals("websocket")) { | |||
FragmentedMessage fragmented = new FragmentedMessage( | |||
@@ -246,11 +266,15 @@ public class AtmospherePushConnection implements PushConnection { | |||
} else { | |||
doPush(socket, message.toJson()); | |||
} | |||
break; | |||
case DISCONNECT_PENDING: | |||
case DISCONNECTED: | |||
throw new IllegalStateException("Can not push after disconnecting"); | |||
return; | |||
} | |||
if (state == State.CONNECT_PENDING) { | |||
getConnectionStateHandler().pushNotConnected(message); | |||
return; | |||
} | |||
throw new IllegalStateException("Can not push after disconnecting"); | |||
} | |||
protected AtmosphereConfiguration getConfig() { | |||
@@ -280,14 +304,10 @@ public class AtmospherePushConnection implements PushConnection { | |||
*/ | |||
protected void onConnect(AtmosphereResponse response) { | |||
transport = response.getTransport(); | |||
switch (state) { | |||
case CONNECT_PENDING: | |||
state = State.CONNECTED; | |||
for (JsonObject message : messageQueue) { | |||
push(message); | |||
} | |||
messageQueue.clear(); | |||
getConnectionStateHandler().pushOk(this); | |||
break; | |||
case DISCONNECT_PENDING: | |||
// Set state to connected to make disconnect close the connection | |||
@@ -335,11 +355,16 @@ public class AtmospherePushConnection implements PushConnection { | |||
protected void onMessage(AtmosphereResponse response) { | |||
String message = response.getResponseBody(); | |||
if (message.startsWith("for(;;);")) { | |||
getLogger().info("Received push message: " + message); | |||
// "for(;;);[{json}]" -> "{json}" | |||
message = message.substring(9, message.length() - 1); | |||
connection.handlePushMessage(message); | |||
ValueMap json = MessageHandler.parseWrappedJson(message); | |||
if (json == null) { | |||
// Invalid string (not wrapped as expected) | |||
getConnectionStateHandler().pushInvalidContent(this, message); | |||
return; | |||
} else { | |||
getLogger().info( | |||
"Received push (" + getTransportType() + ") message: " | |||
+ message); | |||
connection.getMessageHandler().handleMessage(json); | |||
} | |||
} | |||
@@ -361,32 +386,25 @@ public class AtmospherePushConnection implements PushConnection { | |||
*/ | |||
protected void onError(AtmosphereResponse response) { | |||
state = State.DISCONNECTED; | |||
errorHandler.onError("Push connection using " | |||
+ getConfig().getTransport() + " failed!", | |||
response.getStatusCode()); | |||
getConnectionStateHandler().pushError(this, response); | |||
} | |||
protected void onClose(AtmosphereResponse response) { | |||
getLogger().info("Push connection closed"); | |||
state = State.CONNECT_PENDING; | |||
getConnectionStateHandler().pushClosed(this, response); | |||
} | |||
protected void onClientTimeout(AtmosphereResponse response) { | |||
state = State.DISCONNECTED; | |||
errorHandler | |||
.onError( | |||
"Client unexpectedly disconnected. Ensure client timeout is disabled.", | |||
-1); | |||
getConnectionStateHandler().pushClientTimeout(this, response); | |||
} | |||
protected void onReconnect(JavaScriptObject request, | |||
final AtmosphereResponse response) { | |||
if (state == State.CONNECTED) { | |||
getLogger() | |||
.fine("No onClose was received before reconnect. Forcing state to closed."); | |||
state = State.CONNECT_PENDING; | |||
} | |||
getLogger().info("Reopening push connection"); | |||
getConnectionStateHandler().pushReconnectPending(this); | |||
} | |||
public static abstract class AbstractJSO extends JavaScriptObject { | |||
@@ -557,10 +575,8 @@ public class AtmospherePushConnection implements PushConnection { | |||
@Override | |||
public void onError(ResourceLoadEvent event) { | |||
errorHandler.onError( | |||
event.getResourceUrl() | |||
+ " could not be loaded. Push will not work.", | |||
0); | |||
getConnectionStateHandler().pushScriptLoadError( | |||
event.getResourceUrl()); | |||
} | |||
}); | |||
} | |||
@@ -578,11 +594,6 @@ public class AtmospherePushConnection implements PushConnection { | |||
return pushJs; | |||
} | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.client.communication.PushConnection#getTransportType() | |||
*/ | |||
@Override | |||
public String getTransportType() { | |||
return transport; | |||
@@ -591,4 +602,9 @@ public class AtmospherePushConnection implements PushConnection { | |||
private static Logger getLogger() { | |||
return Logger.getLogger(AtmospherePushConnection.class.getName()); | |||
} | |||
private ConnectionStateHandler getConnectionStateHandler() { | |||
return connection.getConnectionStateHandler(); | |||
} | |||
} |
@@ -0,0 +1,202 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.http.client.Request; | |||
import com.google.gwt.http.client.Response; | |||
import com.vaadin.client.ApplicationConnection; | |||
import elemental.json.JsonObject; | |||
/** | |||
* Interface for handling problems and other events which occur during | |||
* communication with the server. | |||
* | |||
* The handler is responsible for handling any problem in XHR, heartbeat and | |||
* push connections in a way it sees fit. The default implementation is | |||
* {@link DefaultConnectionStateHandler}. | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface ConnectionStateHandler { | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the application connection this instance is connected to | |||
*/ | |||
void setConnection(ApplicationConnection connection); | |||
/** | |||
* Called when an exception occurs during a {@link Heartbeat} request | |||
* | |||
* @param request | |||
* The heartbeat request | |||
* @param exception | |||
* The exception which occurred | |||
*/ | |||
void heartbeatException(Request request, Throwable exception); | |||
/** | |||
* Called when a heartbeat request returns a status code other than OK (200) | |||
* | |||
* @param request | |||
* The heartbeat request | |||
* @param response | |||
* The heartbeat response | |||
*/ | |||
void heartbeatInvalidStatusCode(Request request, Response response); | |||
/** | |||
* Called when a {@link Heartbeat} request succeeds | |||
*/ | |||
void heartbeatOk(); | |||
/** | |||
* Called when the push connection to the server is closed. This might | |||
* result in the push connection trying a fallback connection method, trying | |||
* to reconnect to the server or might just be an indication that the | |||
* connection was intentionally closed ("unsubscribe"), | |||
* | |||
* @param pushConnection | |||
* The push connection which was closed | |||
* @param response | |||
* An object containing response data | |||
*/ | |||
void pushClosed(PushConnection pushConnection, | |||
JavaScriptObject responseObject); | |||
/** | |||
* Called when a client side timeout occurs before a push connection to the | |||
* server completes. | |||
* | |||
* The client side timeout causes a disconnection of the push connection and | |||
* no reconnect will be attempted after this method is called, | |||
* | |||
* @param pushConnection | |||
* The push connection which timed out | |||
* @param response | |||
* An object containing response data | |||
*/ | |||
void pushClientTimeout(PushConnection pushConnection, | |||
JavaScriptObject response); | |||
/** | |||
* Called when a fatal error fatal error occurs in the push connection. | |||
* | |||
* The push connection will not try to recover from this situation itself | |||
* and typically the problem handler should not try to do automatic recovery | |||
* either. The cause can be e.g. maximum number of reconnection attempts | |||
* have been reached, neither the selected transport nor the fallback | |||
* transport can be used or similar. | |||
* | |||
* @param pushConnection | |||
* The push connection where the error occurred | |||
* @param response | |||
* An object containing response data | |||
*/ | |||
void pushError(PushConnection pushConnection, JavaScriptObject response); | |||
/** | |||
* Called when the push connection has lost the connection to the server and | |||
* will proceed to try to re-establish the connection | |||
* | |||
* @param pushConnection | |||
* The push connection which will be reconnected | |||
*/ | |||
void pushReconnectPending(PushConnection pushConnection); | |||
/** | |||
* Called when the push connection to the server has been established. | |||
* | |||
* @param pushConnection | |||
* The push connection which was established | |||
*/ | |||
void pushOk(PushConnection pushConnection); | |||
/** | |||
* Called when the required push script could not be loaded | |||
* | |||
* @param resourceUrl | |||
* The URL which was used for loading the script | |||
*/ | |||
void pushScriptLoadError(String resourceUrl); | |||
/** | |||
* Called when an exception occurs during an XmlHttpRequest request to the | |||
* server. | |||
* | |||
* @param xhrConnectionError | |||
* An event containing what was being sent to the server and what | |||
* exception occurred | |||
*/ | |||
void xhrException(XhrConnectionError xhrConnectionError); | |||
/** | |||
* Called when invalid content (not JSON) was returned from the server as | |||
* the result of an XmlHttpRequest request | |||
* | |||
* @param communicationProblemEvent | |||
* An event containing what was being sent to the server and what | |||
* was returned | |||
*/ | |||
void xhrInvalidContent(XhrConnectionError xhrConnectionError); | |||
/** | |||
* Called when invalid status code (not 200) was returned by the server as | |||
* the result of an XmlHttpRequest. | |||
* | |||
* @param communicationProblemEvent | |||
* An event containing what was being sent to the server and what | |||
* was returned | |||
*/ | |||
void xhrInvalidStatusCode(XhrConnectionError xhrConnectionError); | |||
/** | |||
* Called whenever a XmlHttpRequest to the server completes successfully | |||
*/ | |||
void xhrOk(); | |||
/** | |||
* Called when a message is to be sent to the server through the push | |||
* channel but the push channel is not connected | |||
* | |||
* @param payload | |||
* The payload to send to the server | |||
*/ | |||
void pushNotConnected(JsonObject payload); | |||
/** | |||
* Called when invalid content (not JSON) was pushed from the server through | |||
* the push connection | |||
* | |||
* @param communicationProblemEvent | |||
* An event containing what was being sent to the server and what | |||
* was returned | |||
*/ | |||
void pushInvalidContent(PushConnection pushConnection, String message); | |||
/** | |||
* Called when some part of the reconnect dialog configuration has been | |||
* changed. | |||
*/ | |||
void configurationUpdated(); | |||
} |
@@ -0,0 +1,597 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.core.client.JavaScriptObject; | |||
import com.google.gwt.core.shared.GWT; | |||
import com.google.gwt.http.client.Request; | |||
import com.google.gwt.http.client.Response; | |||
import com.google.gwt.regexp.shared.MatchResult; | |||
import com.google.gwt.regexp.shared.RegExp; | |||
import com.google.gwt.user.client.Timer; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedHandler; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.communication.AtmospherePushConnection.AtmosphereResponse; | |||
import com.vaadin.shared.ui.ui.UIState.ReconnectDialogConfigurationState; | |||
import elemental.json.JsonObject; | |||
/** | |||
* Default implementation of the connection state handler. | |||
* <p> | |||
* Handles temporary errors by showing a reconnect dialog to the user while | |||
* trying to re-establish the connection to the server and re-send the pending | |||
* message. | |||
* <p> | |||
* Handles permanent errors by showing a critical system notification to the | |||
* user | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DefaultConnectionStateHandler implements ConnectionStateHandler { | |||
private ApplicationConnection connection; | |||
private ReconnectDialog reconnectDialog = GWT.create(ReconnectDialog.class); | |||
private int reconnectAttempt = 0; | |||
private Type reconnectionCause = null; | |||
private Timer scheduledReconnect; | |||
private Timer dialogShowTimer = new Timer() { | |||
@Override | |||
public void run() { | |||
showDialog(); | |||
} | |||
}; | |||
protected enum Type { | |||
HEARTBEAT(0), PUSH(1), XHR(2); | |||
private int priority; | |||
private Type(int priority) { | |||
this.priority = priority; | |||
} | |||
public boolean isMessage() { | |||
return this == PUSH || this == XHR; | |||
} | |||
/** | |||
* Checks if this type is of higher priority than the given type | |||
* | |||
* @param type | |||
* the type to compare to | |||
* @return true if this type has higher priority than the given type, | |||
* false otherwise | |||
*/ | |||
public boolean isHigherPriorityThan(Type type) { | |||
return priority > type.priority; | |||
} | |||
} | |||
@Override | |||
public void setConnection(ApplicationConnection connection) { | |||
this.connection = connection; | |||
connection.addHandler(ApplicationStoppedEvent.TYPE, | |||
new ApplicationStoppedHandler() { | |||
@Override | |||
public void onApplicationStopped( | |||
ApplicationStoppedEvent event) { | |||
if (isReconnecting()) { | |||
giveUp(); | |||
} | |||
if (scheduledReconnect != null | |||
&& scheduledReconnect.isRunning()) { | |||
scheduledReconnect.cancel(); | |||
} | |||
} | |||
}); | |||
// Allow dialog to cache needed resources to make them available when we | |||
// are offline | |||
reconnectDialog.preload(connection); | |||
}; | |||
/** | |||
* Checks if we are currently trying to reconnect | |||
* | |||
* @return true if we have noted a problem and are trying to re-establish | |||
* server connection, false otherwise | |||
*/ | |||
private boolean isReconnecting() { | |||
return reconnectionCause != null; | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(DefaultConnectionStateHandler.class.getName()); | |||
} | |||
/** | |||
* Returns the connection this handler is connected to | |||
* | |||
* @return the connection for this handler | |||
*/ | |||
protected ApplicationConnection getConnection() { | |||
return connection; | |||
} | |||
@Override | |||
public void xhrException(XhrConnectionError xhrConnectionError) { | |||
debug("xhrException"); | |||
handleRecoverableError(Type.XHR, xhrConnectionError.getPayload()); | |||
} | |||
@Override | |||
public void heartbeatException(Request request, Throwable exception) { | |||
getLogger().severe("Heartbeat exception: " + exception.getMessage()); | |||
handleRecoverableError(Type.HEARTBEAT, null); | |||
} | |||
@Override | |||
public void heartbeatInvalidStatusCode(Request request, Response response) { | |||
int statusCode = response.getStatusCode(); | |||
getLogger().warning("Heartbeat request returned " + statusCode); | |||
if (response.getStatusCode() == Response.SC_GONE) { | |||
// Session expired | |||
getConnection().showSessionExpiredError(null); | |||
stopApplication(); | |||
} else if (response.getStatusCode() == Response.SC_NOT_FOUND) { | |||
// UI closed, do nothing as the UI will react to this | |||
// Should not trigger reconnect dialog as this will prevent user | |||
// input | |||
} else { | |||
handleRecoverableError(Type.HEARTBEAT, null); | |||
} | |||
} | |||
@Override | |||
public void heartbeatOk() { | |||
debug("heartbeatOk"); | |||
if (isReconnecting()) { | |||
resolveTemporaryError(Type.HEARTBEAT); | |||
} | |||
} | |||
private void debug(String msg) { | |||
if (false) { | |||
getLogger().warning(msg); | |||
} | |||
} | |||
/** | |||
* Called whenever an error occurs in communication which should be handled | |||
* by showing the reconnect dialog and retrying communication until | |||
* successful again | |||
* | |||
* @param type | |||
* The type of failure detected | |||
* @param payload | |||
* The message which did not reach the server, or null if no | |||
* message was involved (heartbeat or push connection failed) | |||
*/ | |||
protected void handleRecoverableError(Type type, final JsonObject payload) { | |||
debug("handleTemporaryError(" + type + ")"); | |||
if (!connection.isApplicationRunning()) { | |||
return; | |||
} | |||
if (!isReconnecting()) { | |||
// First problem encounter | |||
reconnectionCause = type; | |||
getLogger().warning("Reconnecting because of " + type + " failure"); | |||
// Precaution only as there should never be a dialog at this point | |||
// and no timer running | |||
stopDialogTimer(); | |||
if (isDialogVisible()) { | |||
hideDialog(); | |||
} | |||
// Show dialog after grace period, still continue to try to | |||
// reconnect even before it is shown | |||
dialogShowTimer.schedule(getConfiguration().dialogGracePeriod); | |||
} else { | |||
// We are currently trying to reconnect | |||
// Priority is HEARTBEAT -> PUSH -> XHR | |||
// If a higher priority issues is resolved, we can assume the lower | |||
// one will be also | |||
if (type.isHigherPriorityThan(reconnectionCause)) { | |||
getLogger().warning( | |||
"Now reconnecting because of " + type + " failure"); | |||
reconnectionCause = type; | |||
} | |||
} | |||
if (reconnectionCause != type) { | |||
return; | |||
} | |||
reconnectAttempt++; | |||
getLogger().info( | |||
"Reconnect attempt " + reconnectAttempt + " for " + type); | |||
if (reconnectAttempt >= getConfiguration().reconnectAttempts) { | |||
// Max attempts reached, stop trying | |||
giveUp(); | |||
} else { | |||
updateDialog(); | |||
scheduleReconnect(payload); | |||
} | |||
} | |||
/** | |||
* Called after a problem occurred. | |||
* | |||
* This method is responsible for re-sending the payload to the server (if | |||
* not null) or re-send a heartbeat request at some point | |||
* | |||
* @param payload | |||
* the payload that did not reach the server, null if the problem | |||
* was detected by a heartbeat | |||
*/ | |||
protected void scheduleReconnect(final JsonObject payload) { | |||
// Here and not in timer to avoid TB for getting in between | |||
// The request is still open at this point to avoid interference, so we | |||
// do not need to start a new one | |||
if (reconnectAttempt == 1) { | |||
// Try once immediately | |||
doReconnect(payload); | |||
} else { | |||
scheduledReconnect = new Timer() { | |||
@Override | |||
public void run() { | |||
scheduledReconnect = null; | |||
doReconnect(payload); | |||
} | |||
}; | |||
scheduledReconnect.schedule(getConfiguration().reconnectInterval); | |||
} | |||
} | |||
/** | |||
* Re-sends the payload to the server (if not null) or re-sends a heartbeat | |||
* request immediately | |||
* | |||
* @param payload | |||
* the payload that did not reach the server, null if the problem | |||
* was detected by a heartbeat | |||
*/ | |||
protected void doReconnect(JsonObject payload) { | |||
if (!connection.isApplicationRunning()) { | |||
// This should not happen as nobody should call this if the | |||
// application has been stopped | |||
getLogger() | |||
.warning( | |||
"Trying to reconnect after application has been stopped. Giving up"); | |||
return; | |||
} | |||
if (payload != null) { | |||
getLogger().info("Re-sending last message to the server..."); | |||
getConnection().getMessageSender().send(payload); | |||
} else { | |||
// Use heartbeat | |||
getLogger().info("Trying to re-establish server connection..."); | |||
getConnection().getHeartbeat().send(); | |||
} | |||
} | |||
/** | |||
* Called whenever a reconnect attempt fails to allow updating of dialog | |||
* contents | |||
*/ | |||
protected void updateDialog() { | |||
reconnectDialog.setText(getDialogText(reconnectAttempt)); | |||
} | |||
/** | |||
* Called when we should give up trying to reconnect and let the user decide | |||
* how to continue | |||
* | |||
*/ | |||
protected void giveUp() { | |||
reconnectionCause = null; | |||
endRequest(); | |||
stopDialogTimer(); | |||
if (!isDialogVisible()) { | |||
// It SHOULD always be visible at this point, unless you have a | |||
// really strange configuration (grace time longer than total | |||
// reconnect time) | |||
showDialog(); | |||
} | |||
reconnectDialog.setText(getDialogTextGaveUp(reconnectAttempt)); | |||
reconnectDialog.setReconnecting(false); | |||
// Stopping the application stops heartbeats and push | |||
connection.setApplicationRunning(false); | |||
} | |||
/** | |||
* Ensures the reconnect dialog does not popup some time from now | |||
*/ | |||
private void stopDialogTimer() { | |||
if (dialogShowTimer.isRunning()) { | |||
dialogShowTimer.cancel(); | |||
} | |||
} | |||
/** | |||
* Checks if the reconnect dialog is visible to the user | |||
* | |||
* @return true if the user can see the dialog, false otherwise | |||
*/ | |||
protected boolean isDialogVisible() { | |||
return reconnectDialog.isVisible(); | |||
} | |||
/** | |||
* Called when the reconnect dialog should be shown. This is typically when | |||
* N seconds has passed since a problem with the connection has been | |||
* detected | |||
*/ | |||
protected void showDialog() { | |||
reconnectDialog.setReconnecting(true); | |||
reconnectDialog.show(connection); | |||
// We never want to show loading indicator and reconnect dialog at the | |||
// same time | |||
connection.getLoadingIndicator().hide(); | |||
} | |||
/** | |||
* Called when the reconnect dialog should be hidden. | |||
*/ | |||
protected void hideDialog() { | |||
reconnectDialog.hide(); | |||
} | |||
/** | |||
* Gets the text to show in the reconnect dialog after giving up (reconnect | |||
* limit reached) | |||
* | |||
* @param reconnectAttempt | |||
* The number of the current reconnection attempt | |||
* @return The text to show in the reconnect dialog after giving up | |||
*/ | |||
protected String getDialogTextGaveUp(int reconnectAttempt) { | |||
return getConfiguration().dialogTextGaveUp.replace("{0}", | |||
reconnectAttempt + ""); | |||
} | |||
/** | |||
* Gets the text to show in the reconnect dialog | |||
* | |||
* @param reconnectAttempt | |||
* The number of the current reconnection attempt | |||
* @return The text to show in the reconnect dialog | |||
*/ | |||
protected String getDialogText(int reconnectAttempt) { | |||
return getConfiguration().dialogText.replace("{0}", reconnectAttempt | |||
+ ""); | |||
} | |||
@Override | |||
public void configurationUpdated() { | |||
// All other properties are fetched directly from the state when needed | |||
reconnectDialog.setModal(getConfiguration().dialogModal); | |||
} | |||
private ReconnectDialogConfigurationState getConfiguration() { | |||
return connection.getUIConnector().getState().reconnectDialogConfiguration; | |||
} | |||
@Override | |||
public void xhrInvalidContent(XhrConnectionError xhrConnectionError) { | |||
debug("xhrInvalidContent"); | |||
endRequest(); | |||
String responseText = xhrConnectionError.getResponse().getText(); | |||
/* | |||
* A servlet filter or equivalent may have intercepted the request and | |||
* served non-UIDL content (for instance, a login page if the session | |||
* has expired.) If the response contains a magic substring, do a | |||
* synchronous refresh. See #8241. | |||
*/ | |||
MatchResult refreshToken = RegExp.compile( | |||
ApplicationConnection.UIDL_REFRESH_TOKEN | |||
+ "(:\\s*(.*?))?(\\s|$)").exec(responseText); | |||
if (refreshToken != null) { | |||
WidgetUtil.redirect(refreshToken.getGroup(2)); | |||
} else { | |||
handleUnrecoverableCommunicationError( | |||
"Invalid JSON response from server: " + responseText, | |||
xhrConnectionError); | |||
} | |||
} | |||
@Override | |||
public void pushInvalidContent(PushConnection pushConnection, String message) { | |||
debug("pushInvalidContent"); | |||
if (pushConnection.isBidirectional()) { | |||
// We can't be sure that what was pushed was actually a response but | |||
// at this point it should not really matter, as something is | |||
// seriously broken. | |||
endRequest(); | |||
} | |||
// Do nothing special for now. Should likely do the same as | |||
// xhrInvalidContent | |||
handleUnrecoverableCommunicationError("Invalid JSON from server: " | |||
+ message, null); | |||
} | |||
@Override | |||
public void xhrInvalidStatusCode(XhrConnectionError xhrConnectionError) { | |||
debug("xhrInvalidStatusCode"); | |||
Response response = xhrConnectionError.getResponse(); | |||
int statusCode = response.getStatusCode(); | |||
getLogger().warning("Server returned " + statusCode + " for xhr"); | |||
if (statusCode == 401) { | |||
// Authentication/authorization failed, no need to re-try | |||
endRequest(); | |||
handleUnauthorized(xhrConnectionError); | |||
return; | |||
} else { | |||
// 404, 408 and other 4xx codes CAN be temporary when you have a | |||
// proxy between the client and the server and e.g. restart the | |||
// server | |||
// 5xx codes may or may not be temporary | |||
handleRecoverableError(Type.XHR, xhrConnectionError.getPayload()); | |||
} | |||
} | |||
/** | |||
* @since | |||
*/ | |||
private void endRequest() { | |||
getConnection().getMessageSender().endRequest(); | |||
} | |||
protected void handleUnauthorized(XhrConnectionError xhrConnectionError) { | |||
/* | |||
* Authorization has failed (401). Could be that the session has timed | |||
* out. | |||
*/ | |||
connection.showAuthenticationError(""); | |||
stopApplication(); | |||
} | |||
private void stopApplication() { | |||
// Consider application not running any more and prevent all | |||
// future requests | |||
connection.setApplicationRunning(false); | |||
} | |||
private void handleUnrecoverableCommunicationError(String details, | |||
XhrConnectionError xhrConnectionError) { | |||
int statusCode = -1; | |||
if (xhrConnectionError != null) { | |||
Response response = xhrConnectionError.getResponse(); | |||
if (response != null) { | |||
statusCode = response.getStatusCode(); | |||
} | |||
} | |||
connection.handleCommunicationError(details, statusCode); | |||
stopApplication(); | |||
} | |||
@Override | |||
public void xhrOk() { | |||
debug("xhrOk"); | |||
if (isReconnecting()) { | |||
resolveTemporaryError(Type.XHR); | |||
} | |||
} | |||
private void resolveTemporaryError(Type type) { | |||
debug("resolveTemporaryError(" + type + ")"); | |||
if (reconnectionCause != type) { | |||
// Waiting for some other problem to be resolved | |||
return; | |||
} | |||
reconnectionCause = null; | |||
reconnectAttempt = 0; | |||
// IF reconnect happens during grace period, make sure the dialog is not | |||
// shown and does not popup later | |||
stopDialogTimer(); | |||
hideDialog(); | |||
getLogger().info("Re-established connection to server"); | |||
} | |||
@Override | |||
public void pushOk(PushConnection pushConnection) { | |||
debug("pushOk()"); | |||
if (isReconnecting()) { | |||
resolveTemporaryError(Type.PUSH); | |||
} | |||
} | |||
@Override | |||
public void pushScriptLoadError(String resourceUrl) { | |||
connection.handleCommunicationError(resourceUrl | |||
+ " could not be loaded. Push will not work.", 0); | |||
} | |||
@Override | |||
public void pushNotConnected(JsonObject payload) { | |||
debug("pushNotConnected()"); | |||
handleRecoverableError(Type.PUSH, payload); | |||
} | |||
@Override | |||
public void pushReconnectPending(PushConnection pushConnection) { | |||
debug("pushReconnectPending(" + pushConnection.getTransportType() + ")"); | |||
getLogger().info("Reopening push connection"); | |||
if (pushConnection.isBidirectional()) { | |||
// Lost connection for a connection which will tell us when the | |||
// connection is available again | |||
handleRecoverableError(Type.PUSH, null); | |||
} else { | |||
// Lost connection for a connection we do not necessarily know when | |||
// it is available again (long polling behind proxy). Do nothing and | |||
// show reconnect dialog if the user does something and the XHR | |||
// fails | |||
} | |||
} | |||
@Override | |||
public void pushError(PushConnection pushConnection, | |||
JavaScriptObject response) { | |||
debug("pushError()"); | |||
connection.handleCommunicationError("Push connection using " | |||
+ ((AtmosphereResponse) response).getTransport() + " failed!", | |||
-1); | |||
} | |||
@Override | |||
public void pushClientTimeout(PushConnection pushConnection, | |||
JavaScriptObject response) { | |||
debug("pushClientTimeout()"); | |||
// TODO Reconnect, allowing client timeout to be set | |||
// https://dev.vaadin.com/ticket/18429 | |||
connection | |||
.handleCommunicationError( | |||
"Client unexpectedly disconnected. Ensure client timeout is disabled.", | |||
-1); | |||
} | |||
@Override | |||
public void pushClosed(PushConnection pushConnection, | |||
JavaScriptObject response) { | |||
debug("pushClosed()"); | |||
getLogger().info("Push connection closed"); | |||
} | |||
} |
@@ -0,0 +1,117 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import com.google.gwt.core.client.Scheduler; | |||
import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.google.gwt.core.shared.GWT; | |||
import com.google.gwt.dom.client.Style.Visibility; | |||
import com.google.gwt.event.dom.client.ClickEvent; | |||
import com.google.gwt.event.dom.client.ClickHandler; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.user.client.ui.FlowPanel; | |||
import com.google.gwt.user.client.ui.HTML; | |||
import com.google.gwt.user.client.ui.Label; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.WidgetUtil; | |||
import com.vaadin.client.ui.VOverlay; | |||
/** | |||
* The default implementation of the reconnect dialog | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class DefaultReconnectDialog extends VOverlay implements ReconnectDialog { | |||
private static final String STYLE_RECONNECTING = "active"; | |||
public Label label; | |||
private HandlerRegistration clickHandler = null; | |||
public DefaultReconnectDialog() { | |||
super(false, true); | |||
addStyleName("v-reconnect-dialog"); | |||
FlowPanel root = new FlowPanel("div"); | |||
HTML spinner = new HTML(); | |||
spinner.addStyleName("spinner"); | |||
label = GWT.create(Label.class); | |||
label.addStyleName("text"); | |||
root.add(spinner); | |||
root.add(label); | |||
setWidget(root); | |||
} | |||
@Override | |||
public void setText(String text) { | |||
label.setText(text); | |||
} | |||
@Override | |||
public void setReconnecting(boolean reconnecting) { | |||
setStyleName(STYLE_RECONNECTING, reconnecting); | |||
// Click to refresh after giving up | |||
if (!reconnecting) { | |||
clickHandler = addDomHandler(new ClickHandler() { | |||
@Override | |||
public void onClick(ClickEvent event) { | |||
// refresh | |||
WidgetUtil.redirect(null); | |||
} | |||
}, ClickEvent.getType()); | |||
} else { | |||
if (clickHandler != null) { | |||
clickHandler.removeHandler(); | |||
} | |||
} | |||
} | |||
@Override | |||
public void show(ApplicationConnection connection) { | |||
ac = connection; | |||
show(); | |||
} | |||
@Override | |||
public void setPopupPosition(int left, int top) { | |||
// Don't set inline styles for position, handle it in the theme | |||
} | |||
@Override | |||
public void preload(ApplicationConnection connection) { | |||
setModal(false); // Don't interfere with application use | |||
show(connection); | |||
getElement().getStyle().setVisibility(Visibility.HIDDEN); | |||
setStyleName(STYLE_RECONNECTING, true); | |||
Scheduler.get().scheduleDeferred(new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
getElement().getStyle().setVisibility(Visibility.VISIBLE); | |||
setStyleName(STYLE_RECONNECTING, false); | |||
hide(); | |||
} | |||
}); | |||
} | |||
} |
@@ -25,7 +25,6 @@ import com.google.gwt.http.client.Response; | |||
import com.google.gwt.user.client.Timer; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; | |||
import com.vaadin.client.ApplicationConnection.ConnectionStatusEvent; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.ui.ui.UIConstants; | |||
import com.vaadin.shared.util.SharedUtil; | |||
@@ -96,41 +95,24 @@ public class Heartbeat { | |||
public void onResponseReceived(Request request, Response response) { | |||
int status = response.getStatusCode(); | |||
// Notify network observers about response status | |||
connection.fireEvent(new ConnectionStatusEvent(status)); | |||
if (status == Response.SC_OK) { | |||
getLogger().fine("Heartbeat response OK"); | |||
} else if (status == 0) { | |||
getLogger().warning( | |||
"Failed sending heartbeat, server is unreachable, retrying in " | |||
+ interval + "secs."); | |||
} else if (status >= 500) { | |||
getLogger().warning( | |||
"Failed sending heartbeat, see server logs, retrying in " | |||
+ interval + "secs."); | |||
} else if (status == Response.SC_GONE) { | |||
connection.showSessionExpiredError(null); | |||
// If session is expired break the loop | |||
return; | |||
connection.getConnectionStateHandler().heartbeatOk(); | |||
} else { | |||
getLogger().warning( | |||
"Failed sending heartbeat to server. Error code: " | |||
+ status); | |||
// Handler should stop the application if heartbeat should | |||
// no longer be sent | |||
connection.getConnectionStateHandler() | |||
.heartbeatInvalidStatusCode(request, response); | |||
} | |||
// Don't break the loop | |||
schedule(); | |||
} | |||
@Override | |||
public void onError(Request request, Throwable exception) { | |||
getLogger().severe( | |||
"Exception sending heartbeat: " | |||
+ exception.getMessage()); | |||
// Notify network observers about response status | |||
connection.fireEvent(new ConnectionStatusEvent(0)); | |||
// Don't break the loop | |||
// Handler should stop the application if heartbeat should no | |||
// longer be sent | |||
connection.getConnectionStateHandler().heartbeatException( | |||
request, exception); | |||
schedule(); | |||
} | |||
}; |
@@ -0,0 +1,410 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.core.client.GWT; | |||
import com.google.gwt.core.client.Scheduler; | |||
import com.google.gwt.user.client.Command; | |||
import com.vaadin.client.ApplicationConfiguration; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.RequestStartingEvent; | |||
import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent; | |||
import com.vaadin.client.Util; | |||
import com.vaadin.client.VLoadingIndicator; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.Version; | |||
import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; | |||
import elemental.json.Json; | |||
import elemental.json.JsonArray; | |||
import elemental.json.JsonObject; | |||
/** | |||
* MessageSender is responsible for sending messages to the server. | |||
* <p> | |||
* Internally uses {@link XhrConnection} and/or {@link PushConnection} for | |||
* delivering messages, depending on the application configuration. | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class MessageSender { | |||
private ApplicationConnection connection; | |||
private boolean hasActiveRequest = false; | |||
/** | |||
* Counter for the messages send to the server. First sent message has id 0. | |||
*/ | |||
private int clientToServerMessageId = 0; | |||
private XhrConnection xhrConnection; | |||
private PushConnection push; | |||
public MessageSender() { | |||
xhrConnection = GWT.create(XhrConnection.class); | |||
} | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the application connection this instance is connected to | |||
*/ | |||
public void setConnection(ApplicationConnection connection) { | |||
this.connection = connection; | |||
xhrConnection.setConnection(connection); | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(MessageSender.class.getName()); | |||
} | |||
public void sendInvocationsToServer() { | |||
if (!connection.isApplicationRunning()) { | |||
getLogger() | |||
.warning( | |||
"Trying to send RPC from not yet started or stopped application"); | |||
return; | |||
} | |||
if (hasActiveRequest() || (push != null && !push.isActive())) { | |||
// There is an active request or push is enabled but not active | |||
// -> send when current request completes or push becomes active | |||
} else { | |||
doSendInvocationsToServer(); | |||
} | |||
} | |||
/** | |||
* Sends all pending method invocations (server RPC and legacy variable | |||
* changes) to the server. | |||
* | |||
*/ | |||
private void doSendInvocationsToServer() { | |||
ServerRpcQueue serverRpcQueue = getServerRpcQueue(); | |||
if (serverRpcQueue.isEmpty()) { | |||
return; | |||
} | |||
if (ApplicationConfiguration.isDebugMode()) { | |||
Util.logMethodInvocations(connection, serverRpcQueue.getAll()); | |||
} | |||
boolean showLoadingIndicator = serverRpcQueue.showLoadingIndicator(); | |||
JsonArray reqJson = serverRpcQueue.toJson(); | |||
serverRpcQueue.clear(); | |||
if (reqJson.length() == 0) { | |||
// Nothing to send, all invocations were filtered out (for | |||
// non-existing connectors) | |||
getLogger() | |||
.warning( | |||
"All RPCs filtered out, not sending anything to the server"); | |||
return; | |||
} | |||
JsonObject extraJson = Json.createObject(); | |||
if (!connection.getConfiguration().isWidgetsetVersionSent()) { | |||
extraJson.put(ApplicationConstants.WIDGETSET_VERSION_ID, | |||
Version.getFullVersion()); | |||
connection.getConfiguration().setWidgetsetVersionSent(); | |||
} | |||
if (showLoadingIndicator) { | |||
connection.getLoadingIndicator().trigger(); | |||
} | |||
send(reqJson, extraJson); | |||
} | |||
private ServerRpcQueue getServerRpcQueue() { | |||
return connection.getServerRpcQueue(); | |||
} | |||
/** | |||
* Makes an UIDL request to the server. | |||
* | |||
* @param reqInvocations | |||
* Data containing RPC invocations and all related information. | |||
* @param extraParams | |||
* Parameters that are added to the payload | |||
*/ | |||
protected void send(final JsonArray reqInvocations, | |||
final JsonObject extraJson) { | |||
startRequest(); | |||
JsonObject payload = Json.createObject(); | |||
String csrfToken = getMessageHandler().getCsrfToken(); | |||
if (!csrfToken.equals(ApplicationConstants.CSRF_TOKEN_DEFAULT_VALUE)) { | |||
payload.put(ApplicationConstants.CSRF_TOKEN, csrfToken); | |||
} | |||
payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations); | |||
payload.put(ApplicationConstants.SERVER_SYNC_ID, getMessageHandler() | |||
.getLastSeenServerSyncId()); | |||
payload.put(ApplicationConstants.CLIENT_TO_SERVER_ID, | |||
clientToServerMessageId++); | |||
if (extraJson != null) { | |||
for (String key : extraJson.keys()) { | |||
payload.put(key, extraJson.get(key)); | |||
} | |||
} | |||
send(payload); | |||
} | |||
/** | |||
* Sends an asynchronous or synchronous UIDL request to the server using the | |||
* given URI. | |||
* | |||
* @param uri | |||
* The URI to use for the request. May includes GET parameters | |||
* @param payload | |||
* The contents of the request to send | |||
*/ | |||
public void send(final JsonObject payload) { | |||
if (push != null && push.isBidirectional()) { | |||
push.push(payload); | |||
} else { | |||
xhrConnection.send(payload); | |||
} | |||
} | |||
/** | |||
* Sets the status for the push connection. | |||
* | |||
* @param enabled | |||
* <code>true</code> to enable the push connection; | |||
* <code>false</code> to disable the push connection. | |||
*/ | |||
public void setPushEnabled(boolean enabled) { | |||
final PushConfigurationState pushState = connection.getUIConnector() | |||
.getState().pushConfiguration; | |||
if (enabled && push == null) { | |||
push = GWT.create(PushConnection.class); | |||
push.init(connection, pushState); | |||
} else if (!enabled && push != null && push.isActive()) { | |||
push.disconnect(new Command() { | |||
@Override | |||
public void execute() { | |||
push = null; | |||
/* | |||
* If push has been enabled again while we were waiting for | |||
* the old connection to disconnect, now is the right time | |||
* to open a new connection | |||
*/ | |||
if (pushState.mode.isEnabled()) { | |||
setPushEnabled(true); | |||
} | |||
/* | |||
* Send anything that was enqueued while we waited for the | |||
* connection to close | |||
*/ | |||
if (getServerRpcQueue().isFlushPending()) { | |||
getServerRpcQueue().flush(); | |||
} | |||
} | |||
}); | |||
} | |||
} | |||
public void startRequest() { | |||
if (hasActiveRequest) { | |||
getLogger().severe( | |||
"Trying to start a new request while another is active"); | |||
} | |||
hasActiveRequest = true; | |||
connection.fireEvent(new RequestStartingEvent(connection)); | |||
} | |||
public void endRequest() { | |||
if (!hasActiveRequest) { | |||
getLogger().severe("No active request"); | |||
} | |||
// After sendInvocationsToServer() there may be a new active | |||
// request, so we must set hasActiveRequest to false before, not after, | |||
// the call. | |||
hasActiveRequest = false; | |||
if (connection.isApplicationRunning()) { | |||
if (getServerRpcQueue().isFlushPending()) { | |||
sendInvocationsToServer(); | |||
} | |||
runPostRequestHooks(connection.getConfiguration().getRootPanelId()); | |||
} | |||
// deferring to avoid flickering | |||
Scheduler.get().scheduleDeferred(new Command() { | |||
@Override | |||
public void execute() { | |||
if (!connection.isApplicationRunning() | |||
|| !(hasActiveRequest() || getServerRpcQueue() | |||
.isFlushPending())) { | |||
getLoadingIndicator().hide(); | |||
// If on Liferay and session expiration management is in | |||
// use, extend session duration on each request. | |||
// Doing it here rather than before the request to improve | |||
// responsiveness. | |||
// Postponed until the end of the next request if other | |||
// requests still pending. | |||
extendLiferaySession(); | |||
} | |||
} | |||
}); | |||
connection.fireEvent(new ResponseHandlingEndedEvent(connection)); | |||
} | |||
/** | |||
* Runs possibly registered client side post request hooks. This is expected | |||
* to be run after each uidl request made by Vaadin application. | |||
* | |||
* @param appId | |||
*/ | |||
public static native void runPostRequestHooks(String appId) | |||
/*-{ | |||
if ($wnd.vaadin.postRequestHooks) { | |||
for ( var hook in $wnd.vaadin.postRequestHooks) { | |||
if (typeof ($wnd.vaadin.postRequestHooks[hook]) == "function") { | |||
try { | |||
$wnd.vaadin.postRequestHooks[hook](appId); | |||
} catch (e) { | |||
} | |||
} | |||
} | |||
} | |||
}-*/; | |||
/** | |||
* If on Liferay and logged in, ask the client side session management | |||
* JavaScript to extend the session duration. | |||
* | |||
* Otherwise, Liferay client side JavaScript will explicitly expire the | |||
* session even though the server side considers the session to be active. | |||
* See ticket #8305 for more information. | |||
*/ | |||
public static native void extendLiferaySession() | |||
/*-{ | |||
if ($wnd.Liferay && $wnd.Liferay.Session) { | |||
$wnd.Liferay.Session.extend(); | |||
// if the extend banner is visible, hide it | |||
if ($wnd.Liferay.Session.banner) { | |||
$wnd.Liferay.Session.banner.remove(); | |||
} | |||
} | |||
}-*/; | |||
/** | |||
* Indicates whether or not there are currently active UIDL requests. Used | |||
* internally to sequence requests properly, seldom needed in Widgets. | |||
* | |||
* @return true if there are active requests | |||
*/ | |||
public boolean hasActiveRequest() { | |||
return hasActiveRequest; | |||
} | |||
/** | |||
* Returns a human readable string representation of the method used to | |||
* communicate with the server. | |||
* | |||
* @return A string representation of the current transport type | |||
*/ | |||
public String getCommunicationMethodName() { | |||
String clientToServer = "XHR"; | |||
String serverToClient = "-"; | |||
if (push != null) { | |||
serverToClient = push.getTransportType(); | |||
if (push.isBidirectional()) { | |||
clientToServer = serverToClient; | |||
} | |||
} | |||
return "Client to server: " + clientToServer + ", " | |||
+ "server to client: " + serverToClient; | |||
} | |||
private ConnectionStateHandler getConnectionStateHandler() { | |||
return connection.getConnectionStateHandler(); | |||
} | |||
private MessageHandler getMessageHandler() { | |||
return connection.getMessageHandler(); | |||
} | |||
private VLoadingIndicator getLoadingIndicator() { | |||
return connection.getLoadingIndicator(); | |||
} | |||
/** | |||
* Resynchronize the client side, i.e. reload all component hierarchy and | |||
* state from the server | |||
*/ | |||
public void resynchronize() { | |||
getLogger().info("Resynchronizing from server"); | |||
JsonObject resyncParam = Json.createObject(); | |||
resyncParam.put(ApplicationConstants.RESYNCHRONIZE_ID, true); | |||
send(Json.createArray(), resyncParam); | |||
} | |||
/** | |||
* Used internally to update what the server expects | |||
* | |||
* @param clientToServerMessageId | |||
* the new client id to set | |||
* @param force | |||
* true if the id must be updated, false otherwise | |||
*/ | |||
public void setClientToServerMessageId(int nextExpectedId, boolean force) { | |||
if (nextExpectedId == clientToServerMessageId) { | |||
// No op as everything matches they way it should | |||
return; | |||
} | |||
if (force) { | |||
getLogger().info( | |||
"Forced update of clientId to " + clientToServerMessageId); | |||
clientToServerMessageId = nextExpectedId; | |||
return; | |||
} | |||
if (nextExpectedId > clientToServerMessageId) { | |||
if (clientToServerMessageId == 0) { | |||
// We have never sent a message to the server, so likely the | |||
// server knows better (typical case is that we refreshed a | |||
// @PreserveOnRefresh UI) | |||
getLogger().info( | |||
"Updating client-to-server id to " + nextExpectedId | |||
+ " based on server"); | |||
} else { | |||
getLogger().warning( | |||
"Server expects next client-to-server id to be " | |||
+ nextExpectedId + " but we were going to use " | |||
+ clientToServerMessageId + ". Will use " | |||
+ nextExpectedId + "."); | |||
} | |||
clientToServerMessageId = nextExpectedId; | |||
} else { | |||
// Server has not yet seen all our messages | |||
// Do nothing as they will arrive eventually | |||
} | |||
} | |||
} |
@@ -18,8 +18,8 @@ package com.vaadin.client.communication; | |||
import com.google.gwt.user.client.Command; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; | |||
import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; | |||
import elemental.json.JsonObject; | |||
/** | |||
@@ -41,18 +41,19 @@ public interface PushConnection { | |||
* The ApplicationConnection | |||
*/ | |||
public void init(ApplicationConnection connection, | |||
PushConfigurationState pushConfigurationState, | |||
CommunicationErrorHandler errorHandler); | |||
PushConfigurationState pushConfigurationState); | |||
/** | |||
* Pushes a message to the server. Will throw an exception if the connection | |||
* is not active (see {@link #isActive()}). | |||
* <p> | |||
* Implementation detail: The implementation is responsible for queuing | |||
* messages that are pushed after {@link #init(ApplicationConnection)} has | |||
* been called but before the connection has internally been set up and then | |||
* replay those messages in the original order when the connection has been | |||
* established. | |||
* Implementation detail: If the push connection is not connected and the | |||
* message can thus not be sent, the implementation must call | |||
* {@link ConnectionStateHandler#pushNotConnected(JsonObject)}, which | |||
* will retry the send later. | |||
* <p> | |||
* This method must not be called if the push connection is not | |||
* bidirectional (if {@link #isBidirectional()} returns false) | |||
* | |||
* @param payload | |||
* the payload to push | |||
@@ -102,4 +103,19 @@ public interface PushConnection { | |||
*/ | |||
public String getTransportType(); | |||
/** | |||
* Checks whether this push connection should be used for communication in | |||
* both directions or if an XHR should be used for client to server | |||
* communication. | |||
* | |||
* A bidirectional push connection must be able to reliably inform about its | |||
* connection state. | |||
* | |||
* @since 7.6 | |||
* @return true if the push connection should be used for messages in both | |||
* directions, false if it should only be used for server to client | |||
* messages | |||
*/ | |||
public boolean isBidirectional(); | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import com.vaadin.client.ApplicationConnection; | |||
/** | |||
* Interface which must be implemented by the reconnect dialog | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface ReconnectDialog { | |||
/** | |||
* Sets the main text shown in the dialog | |||
* | |||
* @param text | |||
* the text to show | |||
*/ | |||
void setText(String text); | |||
/** | |||
* Sets the reconnecting state, which is true if we are trying to | |||
* re-establish a connection with the server. | |||
* | |||
* @param reconnecting | |||
* true if we are trying to re-establish the server connection, | |||
* false if we have given up | |||
*/ | |||
void setReconnecting(boolean reconnecting); | |||
/** | |||
* Checks if the reconnect dialog is visible to the user | |||
* | |||
* @return true if the user can see the dialog, false otherwise | |||
*/ | |||
boolean isVisible(); | |||
/** | |||
* Shows the dialog to the user | |||
* | |||
* @param connection | |||
* the application connection this is related to | |||
*/ | |||
void show(ApplicationConnection connection); | |||
/** | |||
* Hides the dialog from the user | |||
*/ | |||
void hide(); | |||
/** | |||
* Sets the modality of the dialog. If the dialog is set to modal, it will | |||
* prevent the usage of the application while the dialog is being shown. If | |||
* not modal, the user can continue to use the application as normally and | |||
* all server events will be queued until connection has been | |||
* re-established. | |||
* | |||
* @param modal | |||
* true to make the dialog modal, false to allow usage while | |||
* dialog is shown | |||
*/ | |||
void setModal(boolean modal); | |||
/** | |||
* Checks the modality of the dialog. | |||
* | |||
* @see #setModal(boolean) | |||
* @return true if the dialog is modal, false otherwise | |||
*/ | |||
boolean isModal(); | |||
/** | |||
* Called once after initialization to allow the reconnect dialog to preload | |||
* required resources, which might not be available when the server | |||
* connection is gone | |||
*/ | |||
void preload(ApplicationConnection connection); | |||
} |
@@ -58,8 +58,12 @@ public class RpcProxy { | |||
MethodInvocation invocation = new MethodInvocation( | |||
connector.getConnectorId(), rpcInterface.getName(), | |||
method.getName(), params); | |||
connector.getConnection().addMethodInvocationToQueue(invocation, | |||
method.isDelayed(), method.isLastOnly()); | |||
ServerRpcQueue serverRpcQueue = ServerRpcQueue.get(connector | |||
.getConnection()); | |||
serverRpcQueue.add(invocation, method.isLastOnly()); | |||
if (!method.isDelayed()) { | |||
serverRpcQueue.flush(); | |||
} | |||
// No RPC iface should have a return value | |||
return null; | |||
} |
@@ -0,0 +1,342 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import java.util.Collection; | |||
import java.util.Iterator; | |||
import java.util.LinkedHashMap; | |||
import java.util.logging.Logger; | |||
import com.google.gwt.core.client.Scheduler; | |||
import com.google.gwt.core.client.Scheduler.ScheduledCommand; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ConnectorMap; | |||
import com.vaadin.client.metadata.Method; | |||
import com.vaadin.client.metadata.NoDataException; | |||
import com.vaadin.client.metadata.Type; | |||
import com.vaadin.client.metadata.TypeDataStore; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.communication.MethodInvocation; | |||
import elemental.json.Json; | |||
import elemental.json.JsonArray; | |||
import elemental.json.JsonValue; | |||
/** | |||
* Manages the queue of server invocations (RPC) which are waiting to be sent to | |||
* the server. | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ServerRpcQueue { | |||
/** | |||
* The pending method invocations that will be send to the server by | |||
* {@link #sendPendingCommand}. The key is defined differently based on | |||
* whether the method invocation is enqueued with lastonly. With lastonly | |||
* enabled, the method signature ( {@link MethodInvocation#getLastOnlyTag()} | |||
* ) is used as the key to make enable removing a previously enqueued | |||
* invocation. Without lastonly, an incremental id based on | |||
* {@link #lastInvocationTag} is used to get unique values. | |||
*/ | |||
private LinkedHashMap<String, MethodInvocation> pendingInvocations = new LinkedHashMap<String, MethodInvocation>(); | |||
private int lastInvocationTag = 0; | |||
protected ApplicationConnection connection; | |||
private boolean flushPending = false; | |||
private boolean flushScheduled = false; | |||
public ServerRpcQueue() { | |||
} | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the application connection this instance is connected to | |||
*/ | |||
public void setConnection(ApplicationConnection connection) { | |||
this.connection = connection; | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(ServerRpcQueue.class.getName()); | |||
} | |||
/** | |||
* Removes any pending invocation of the given method from the queue | |||
* | |||
* @param invocation | |||
* The invocation to remove | |||
*/ | |||
public void removeMatching(MethodInvocation invocation) { | |||
Iterator<MethodInvocation> iter = pendingInvocations.values() | |||
.iterator(); | |||
while (iter.hasNext()) { | |||
MethodInvocation mi = iter.next(); | |||
if (mi.equals(invocation)) { | |||
iter.remove(); | |||
} | |||
} | |||
} | |||
/** | |||
* Adds an explicit RPC method invocation to the send queue. | |||
* | |||
* @param invocation | |||
* RPC method invocation | |||
* @param delayed | |||
* <code>false</code> to trigger sending within a short time | |||
* window (possibly combining subsequent calls to a single | |||
* request), <code>true</code> to let the framework delay sending | |||
* of RPC calls and variable changes until the next non-delayed | |||
* change | |||
* @param lastOnly | |||
* <code>true</code> to remove all previously delayed invocations | |||
* of the same method that were also enqueued with lastonly set | |||
* to <code>true</code>. <code>false</code> to add invocation to | |||
* the end of the queue without touching previously enqueued | |||
* invocations. | |||
*/ | |||
public void add(MethodInvocation invocation, boolean lastOnly) { | |||
if (!connection.isApplicationRunning()) { | |||
getLogger() | |||
.warning( | |||
"Trying to invoke method on not yet started or stopped application"); | |||
return; | |||
} | |||
String tag; | |||
if (lastOnly) { | |||
tag = invocation.getLastOnlyTag(); | |||
assert !tag.matches("\\d+") : "getLastOnlyTag value must have at least one non-digit character"; | |||
pendingInvocations.remove(tag); | |||
} else { | |||
tag = Integer.toString(lastInvocationTag++); | |||
} | |||
pendingInvocations.put(tag, invocation); | |||
} | |||
/** | |||
* Returns a collection of all queued method invocations | |||
* <p> | |||
* The returned collection must not be modified in any way | |||
* | |||
* @return a collection of all queued method invocations | |||
*/ | |||
public Collection<MethodInvocation> getAll() { | |||
return pendingInvocations.values(); | |||
} | |||
/** | |||
* Clears the queue | |||
*/ | |||
public void clear() { | |||
pendingInvocations.clear(); | |||
// Keep tag string short | |||
lastInvocationTag = 0; | |||
flushPending = false; | |||
} | |||
/** | |||
* Returns the current size of the queue | |||
* | |||
* @return the number of invocations in the queue | |||
*/ | |||
public int size() { | |||
return pendingInvocations.size(); | |||
} | |||
/** | |||
* Returns the server RPC queue for the given application | |||
* | |||
* @param connection | |||
* the application connection which owns the queue | |||
* @return the server rpc queue for the given application | |||
*/ | |||
public static ServerRpcQueue get(ApplicationConnection connection) { | |||
return connection.getServerRpcQueue(); | |||
} | |||
/** | |||
* Checks if the queue is empty | |||
* | |||
* @return true if the queue is empty, false otherwise | |||
*/ | |||
public boolean isEmpty() { | |||
return size() == 0; | |||
} | |||
/** | |||
* Triggers a send of server RPC and legacy variable changes to the server. | |||
*/ | |||
public void flush() { | |||
if (flushScheduled) { | |||
return; | |||
} | |||
flushPending = true; | |||
flushScheduled = true; | |||
Scheduler.get().scheduleFinally(scheduledFlushCommand); | |||
} | |||
private final ScheduledCommand scheduledFlushCommand = new ScheduledCommand() { | |||
@Override | |||
public void execute() { | |||
flushScheduled = false; | |||
if (!isFlushPending()) { | |||
// Somebody else cleared the queue before we had the chance | |||
return; | |||
} | |||
connection.getMessageSender().sendInvocationsToServer(); | |||
} | |||
}; | |||
/** | |||
* Checks if a flush operation is pending | |||
* | |||
* @return true if a flush is pending, false otherwise | |||
*/ | |||
public boolean isFlushPending() { | |||
return flushPending; | |||
} | |||
/** | |||
* Checks if a loading indicator should be shown when the RPCs have been | |||
* sent to the server and we are waiting for a response | |||
* | |||
* @return true if a loading indicator should be shown, false otherwise | |||
*/ | |||
public boolean showLoadingIndicator() { | |||
for (MethodInvocation invocation : getAll()) { | |||
if (isLegacyVariableChange(invocation)) { | |||
// Always show loading indicator for legacy requests | |||
return true; | |||
} else if (!isJavascriptRpc(invocation)) { | |||
Type type = new Type(invocation.getInterfaceName(), null); | |||
Method method = type.getMethod(invocation.getMethodName()); | |||
if (!TypeDataStore.isNoLoadingIndicator(method)) { | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
/** | |||
* Returns the current invocations as JSON | |||
* | |||
* @return the current invocations in a JSON format ready to be sent to the | |||
* server | |||
*/ | |||
public JsonArray toJson() { | |||
JsonArray json = Json.createArray(); | |||
if (isEmpty()) { | |||
return json; | |||
} | |||
for (MethodInvocation invocation : getAll()) { | |||
String connectorId = invocation.getConnectorId(); | |||
if (!connectorExists(connectorId)) { | |||
getLogger().info( | |||
"Ignoring RPC for removed connector: " + connectorId | |||
+ ": " + invocation.toString()); | |||
continue; | |||
} | |||
JsonArray invocationJson = Json.createArray(); | |||
invocationJson.set(0, connectorId); | |||
invocationJson.set(1, invocation.getInterfaceName()); | |||
invocationJson.set(2, invocation.getMethodName()); | |||
JsonArray paramJson = Json.createArray(); | |||
Type[] parameterTypes = null; | |||
if (!isLegacyVariableChange(invocation) | |||
&& !isJavascriptRpc(invocation)) { | |||
try { | |||
Type type = new Type(invocation.getInterfaceName(), null); | |||
Method method = type.getMethod(invocation.getMethodName()); | |||
parameterTypes = method.getParameterTypes(); | |||
} catch (NoDataException e) { | |||
throw new RuntimeException("No type data for " | |||
+ invocation.toString(), e); | |||
} | |||
} | |||
for (int i = 0; i < invocation.getParameters().length; ++i) { | |||
// TODO non-static encoder? | |||
Type type = null; | |||
if (parameterTypes != null) { | |||
type = parameterTypes[i]; | |||
} | |||
Object value = invocation.getParameters()[i]; | |||
JsonValue jsonValue = JsonEncoder.encode(value, type, | |||
connection); | |||
paramJson.set(i, jsonValue); | |||
} | |||
invocationJson.set(3, paramJson); | |||
json.set(json.length(), invocationJson); | |||
} | |||
return json; | |||
} | |||
/** | |||
* Checks if the connector with the given id is still ok to use (has not | |||
* been removed) | |||
* | |||
* @param connectorId | |||
* the connector id to check | |||
* @return true if the connector exists, false otherwise | |||
*/ | |||
private boolean connectorExists(String connectorId) { | |||
ConnectorMap connectorMap = ConnectorMap.get(connection); | |||
return connectorMap.hasConnector(connectorId) | |||
|| connectorMap.isDragAndDropPaintable(connectorId); | |||
} | |||
/** | |||
* Checks if the given method invocation originates from Javascript | |||
* | |||
* @param invocation | |||
* the invocation to check | |||
* @return true if the method invocation originates from javascript, false | |||
* otherwise | |||
*/ | |||
public static boolean isJavascriptRpc(MethodInvocation invocation) { | |||
return invocation instanceof JavaScriptMethodInvocation; | |||
} | |||
/** | |||
* Checks if the given method invocation represents a Vaadin 6 variable | |||
* change | |||
* | |||
* @param invocation | |||
* the invocation to check | |||
* @return true if the method invocation is a legacy variable change, false | |||
* otherwise | |||
*/ | |||
public static boolean isLegacyVariableChange(MethodInvocation invocation) { | |||
return ApplicationConstants.UPDATE_VARIABLE_METHOD.equals(invocation | |||
.getInterfaceName()) | |||
&& ApplicationConstants.UPDATE_VARIABLE_METHOD | |||
.equals(invocation.getMethodName()); | |||
} | |||
} |
@@ -30,8 +30,11 @@ public class TranslatedURLReference extends URLReference { | |||
private ApplicationConnection connection; | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the connection to set | |||
* the application connection this instance is connected to | |||
*/ | |||
public void setConnection(ApplicationConnection connection) { | |||
this.connection = connection; |
@@ -0,0 +1,276 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import java.util.Date; | |||
import java.util.logging.Logger; | |||
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.Timer; | |||
import com.google.gwt.user.client.Window; | |||
import com.google.gwt.user.client.Window.ClosingEvent; | |||
import com.google.gwt.user.client.Window.ClosingHandler; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.CommunicationHandler; | |||
import com.vaadin.client.ApplicationConnection.RequestStartingEvent; | |||
import com.vaadin.client.ApplicationConnection.ResponseHandlingEndedEvent; | |||
import com.vaadin.client.ApplicationConnection.ResponseHandlingStartedEvent; | |||
import com.vaadin.client.BrowserInfo; | |||
import com.vaadin.client.ValueMap; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.JsonConstants; | |||
import com.vaadin.shared.ui.ui.UIConstants; | |||
import com.vaadin.shared.util.SharedUtil; | |||
import elemental.json.JsonObject; | |||
/** | |||
* Provides a connection to the /UIDL url on the server and knows how to send | |||
* messages to that end point | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class XhrConnection { | |||
private ApplicationConnection connection; | |||
/** | |||
* Webkit will ignore outgoing requests while waiting for a response to a | |||
* navigation event (indicated by a beforeunload event). When this happens, | |||
* we should keep trying to send the request every now and then until there | |||
* is a response or until it throws an exception saying that it is already | |||
* being sent. | |||
*/ | |||
private boolean webkitMaybeIgnoringRequests = false; | |||
public XhrConnection() { | |||
Window.addWindowClosingHandler(new ClosingHandler() { | |||
@Override | |||
public void onWindowClosing(ClosingEvent event) { | |||
webkitMaybeIgnoringRequests = true; | |||
} | |||
}); | |||
} | |||
/** | |||
* Sets the application connection this instance is connected to. Called | |||
* internally by the framework. | |||
* | |||
* @param connection | |||
* the application connection this instance is connected to | |||
*/ | |||
public void setConnection(ApplicationConnection connection) { | |||
this.connection = connection; | |||
connection.addHandler(ResponseHandlingEndedEvent.TYPE, | |||
new CommunicationHandler() { | |||
@Override | |||
public void onRequestStarting(RequestStartingEvent e) { | |||
} | |||
@Override | |||
public void onResponseHandlingStarted( | |||
ResponseHandlingStartedEvent e) { | |||
} | |||
@Override | |||
public void onResponseHandlingEnded( | |||
ResponseHandlingEndedEvent e) { | |||
webkitMaybeIgnoringRequests = false; | |||
} | |||
}); | |||
} | |||
private static Logger getLogger() { | |||
return Logger.getLogger(XhrConnection.class.getName()); | |||
} | |||
protected XhrResponseHandler createResponseHandler() { | |||
return new XhrResponseHandler(); | |||
} | |||
public class XhrResponseHandler implements RequestCallback { | |||
private JsonObject payload; | |||
private Date requestStartTime; | |||
public XhrResponseHandler() { | |||
} | |||
/** | |||
* Sets the payload which was sent to the server | |||
* | |||
* @param payload | |||
* the payload which was sent to the server | |||
*/ | |||
public void setPayload(JsonObject payload) { | |||
this.payload = payload; | |||
} | |||
@Override | |||
public void onError(Request request, Throwable exception) { | |||
getConnectionStateHandler().xhrException( | |||
new XhrConnectionError(request, payload, exception)); | |||
} | |||
@Override | |||
public void onResponseReceived(Request request, Response response) { | |||
int statusCode = response.getStatusCode(); | |||
if (statusCode != 200) { | |||
// There was a problem | |||
XhrConnectionError problemEvent = new XhrConnectionError( | |||
request, payload, response); | |||
getConnectionStateHandler().xhrInvalidStatusCode(problemEvent); | |||
return; | |||
} | |||
getLogger().info( | |||
"Server visit took " | |||
+ String.valueOf((new Date()).getTime() | |||
- requestStartTime.getTime()) + "ms"); | |||
String contentType = response.getHeader("Content-Type"); | |||
if (contentType == null | |||
|| !contentType.startsWith("application/json")) { | |||
getConnectionStateHandler().xhrInvalidContent( | |||
new XhrConnectionError(request, payload, response)); | |||
return; | |||
} | |||
// for(;;);["+ realJson +"]" | |||
String responseText = response.getText(); | |||
ValueMap json = MessageHandler.parseWrappedJson(responseText); | |||
if (json == null) { | |||
// Invalid string (not wrapped as expected or can't parse) | |||
getConnectionStateHandler().xhrInvalidContent( | |||
new XhrConnectionError(request, payload, response)); | |||
return; | |||
} | |||
getConnectionStateHandler().xhrOk(); | |||
getLogger().info("Received xhr message: " + responseText); | |||
getMessageHandler().handleMessage(json); | |||
} | |||
/** | |||
* Sets the time when the request was sent | |||
* | |||
* @param requestStartTime | |||
* the time when the request was sent | |||
*/ | |||
public void setRequestStartTime(Date requestStartTime) { | |||
this.requestStartTime = requestStartTime; | |||
} | |||
}; | |||
/** | |||
* Sends an asynchronous UIDL request to the server using the given URI. | |||
* | |||
* @param payload | |||
* The URI to use for the request. May includes GET parameters | |||
* @throws RequestException | |||
* if the request could not be sent | |||
*/ | |||
public void send(JsonObject payload) { | |||
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, getUri()); | |||
// TODO enable timeout | |||
// rb.setTimeoutMillis(timeoutMillis); | |||
// TODO this should be configurable | |||
rb.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE); | |||
rb.setRequestData(payload.toJson()); | |||
XhrResponseHandler responseHandler = createResponseHandler(); | |||
responseHandler.setPayload(payload); | |||
responseHandler.setRequestStartTime(new Date()); | |||
rb.setCallback(responseHandler); | |||
getLogger().info("Sending xhr message to server: " + payload.toJson()); | |||
try { | |||
final Request request = rb.send(); | |||
if (webkitMaybeIgnoringRequests && BrowserInfo.get().isWebkit()) { | |||
final int retryTimeout = 250; | |||
new Timer() { | |||
@Override | |||
public void run() { | |||
// Use native js to access private field in Request | |||
if (resendRequest(request) | |||
&& webkitMaybeIgnoringRequests) { | |||
// Schedule retry if still needed | |||
schedule(retryTimeout); | |||
} | |||
} | |||
}.schedule(retryTimeout); | |||
} | |||
} catch (RequestException e) { | |||
getConnectionStateHandler().xhrException( | |||
new XhrConnectionError(null, payload, e)); | |||
} | |||
} | |||
/** | |||
* Retrieves the URI to use when sending RPCs to the server | |||
* | |||
* @return The URI to use for server messages. | |||
*/ | |||
protected String getUri() { | |||
String uri = connection | |||
.translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX | |||
+ ApplicationConstants.UIDL_PATH + '/'); | |||
uri = SharedUtil.addGetParameters(uri, UIConstants.UI_ID_PARAMETER | |||
+ "=" + connection.getConfiguration().getUIId()); | |||
return uri; | |||
} | |||
private ConnectionStateHandler getConnectionStateHandler() { | |||
return connection.getConnectionStateHandler(); | |||
} | |||
private MessageHandler getMessageHandler() { | |||
return connection.getMessageHandler(); | |||
} | |||
private static native boolean resendRequest(Request request) | |||
/*-{ | |||
var xhr = request.@com.google.gwt.http.client.Request::xmlHttpRequest | |||
if (xhr.readyState != 1) { | |||
// Progressed to some other readyState -> no longer blocked | |||
return false; | |||
} | |||
try { | |||
xhr.send(); | |||
return true; | |||
} catch (e) { | |||
// send throws exception if it is running for real | |||
return false; | |||
} | |||
}-*/; | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import com.google.gwt.http.client.Request; | |||
import com.google.gwt.http.client.Response; | |||
import elemental.json.JsonObject; | |||
/** | |||
* XhrConnectionError provides detail about an error which occured during an XHR | |||
* request to the server | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public class XhrConnectionError { | |||
private Throwable exception; | |||
private Request request; | |||
private Response response; | |||
private JsonObject payload; | |||
/** | |||
* Constructs an event from the given request, payload and exception | |||
* | |||
* @param request | |||
* the request which failed | |||
* @param payload | |||
* the payload which was going to the server | |||
* @param exception | |||
* the exception describing the problem | |||
*/ | |||
public XhrConnectionError(Request request, JsonObject payload, | |||
Throwable exception) { | |||
this.request = request; | |||
this.exception = exception; | |||
this.payload = payload; | |||
} | |||
/** | |||
* Constructs an event from the given request, response and payload | |||
* | |||
* @param request | |||
* the request which failed | |||
* @param payload | |||
* the payload which was going to the server | |||
* @param response | |||
* the response for the request | |||
*/ | |||
public XhrConnectionError(Request request, JsonObject payload, | |||
Response response) { | |||
this.request = request; | |||
this.response = response; | |||
this.payload = payload; | |||
} | |||
/** | |||
* Returns the exception which caused the problem, if available | |||
* | |||
* @return the exception which caused the problem, or null if not available | |||
*/ | |||
public Throwable getException() { | |||
return exception; | |||
} | |||
/** | |||
* Returns the request for which the problem occurred | |||
* | |||
* @return the request where the problem occurred | |||
*/ | |||
public Request getRequest() { | |||
return request; | |||
} | |||
/** | |||
* Returns the received response, if available | |||
* | |||
* @return the received response, or null if not available | |||
*/ | |||
public Response getResponse() { | |||
return response; | |||
} | |||
/** | |||
* Returns the payload which was sent to the server | |||
* | |||
* @return the payload which was sent, never null | |||
*/ | |||
public JsonObject getPayload() { | |||
return payload; | |||
} | |||
} |
@@ -166,7 +166,7 @@ public class InfoSection implements Section { | |||
addRow("Theme", connection.getUIConnector().getActiveTheme()); | |||
String communicationMethodInfo = connection | |||
.getCommunicationMethodName(); | |||
.getMessageSender().getCommunicationMethodName(); | |||
int pollInterval = connection.getUIConnector().getState().pollInterval; | |||
if (pollInterval > 0) { | |||
communicationMethodInfo += " (poll interval " + pollInterval |
@@ -24,6 +24,7 @@ import com.google.gwt.core.client.JsArray; | |||
import com.vaadin.client.ServerConnector; | |||
import com.vaadin.client.Util; | |||
import com.vaadin.client.communication.JavaScriptMethodInvocation; | |||
import com.vaadin.client.communication.ServerRpcQueue; | |||
import com.vaadin.client.communication.StateChangeEvent; | |||
import com.vaadin.client.extensions.AbstractExtensionConnector; | |||
import com.vaadin.shared.extension.javascriptmanager.ExecuteJavaScriptRpc; | |||
@@ -122,10 +123,11 @@ public class JavaScriptManagerConnector extends AbstractExtensionConnector { | |||
* Must invoke manually as the RPC interface can't be used in GWT | |||
* because of the JSONArray parameter | |||
*/ | |||
getConnection().addMethodInvocationToQueue( | |||
new JavaScriptMethodInvocation(getConnectorId(), | |||
"com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", | |||
"call", parameters), false, false); | |||
ServerRpcQueue rpcQueue = ServerRpcQueue.get(getConnection()); | |||
rpcQueue.add(new JavaScriptMethodInvocation(getConnectorId(), | |||
"com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", | |||
parameters), false); | |||
rpcQueue.flush(); | |||
} | |||
@Override |
@@ -655,7 +655,7 @@ public class VNotification extends VOverlay { | |||
n.show(html.toString(), VNotification.CENTERED_TOP, | |||
VNotification.STYLE_SYSTEM); | |||
} else { | |||
ApplicationConnection.redirect(url); | |||
WidgetUtil.redirect(url); | |||
} | |||
} | |||
@@ -674,7 +674,7 @@ public class VNotification extends VOverlay { | |||
@Override | |||
public void notificationHidden(HideEvent event) { | |||
ApplicationConnection.redirect(url); | |||
WidgetUtil.redirect(url); | |||
} | |||
} |
@@ -2620,7 +2620,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, | |||
@Override | |||
public void run() { | |||
if (client.hasActiveRequest() || navKeyDown) { | |||
if (client.getMessageSender().hasActiveRequest() | |||
|| navKeyDown) { | |||
// if client connection is busy, don't bother loading it more | |||
VConsole.log("Postponed rowfetch"); | |||
schedule(250); |
@@ -36,9 +36,12 @@ import com.google.gwt.user.client.ui.Panel; | |||
import com.google.gwt.user.client.ui.SimplePanel; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.BrowserInfo; | |||
import com.vaadin.client.ConnectorMap; | |||
import com.vaadin.client.StyleConstants; | |||
import com.vaadin.client.VConsole; | |||
import com.vaadin.client.ui.upload.UploadConnector; | |||
import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy; | |||
import com.vaadin.shared.ui.upload.UploadServerRpc; | |||
/** | |||
* | |||
@@ -246,7 +249,9 @@ public class VUpload extends SimplePanel { | |||
t.cancel(); | |||
} | |||
VConsole.log("VUpload:Submit complete"); | |||
client.sendPendingVariableChanges(); | |||
((UploadConnector) ConnectorMap.get(client) | |||
.getConnector(VUpload.this)).getRpcProxy( | |||
UploadServerRpc.class).poll(); | |||
} | |||
rebuildPanel(); |
@@ -32,7 +32,6 @@ import com.vaadin.shared.ui.Connect; | |||
import com.vaadin.shared.ui.datefield.PopupDateFieldState; | |||
import com.vaadin.shared.ui.datefield.Resolution; | |||
import com.vaadin.ui.DateField; | |||
import com.vaadin.ui.PopupDateField; | |||
@Connect(DateField.class) | |||
public class DateFieldConnector extends TextualDateConnector { | |||
@@ -60,7 +59,7 @@ public class DateFieldConnector extends TextualDateConnector { | |||
* communicated to the server. | |||
*/ | |||
if (getWidget().isImmediate()) { | |||
getConnection().sendPendingVariableChanges(); | |||
getConnection().getServerRpcQueue().flush(); | |||
} | |||
} | |||
}); |
@@ -488,7 +488,8 @@ public class VDragAndDropManager { | |||
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() { | |||
@Override | |||
public boolean execute() { | |||
if (!client.hasActiveRequest()) { | |||
if (!client.getMessageSender() | |||
.hasActiveRequest()) { | |||
removeActiveDragSourceStyleName(dragSource); | |||
return false; | |||
} |
@@ -226,7 +226,7 @@ public abstract class AbstractOrderedLayoutConnector extends | |||
/** | |||
* The id of the previous response for which state changes have been | |||
* processed. If this is the same as the | |||
* {@link ApplicationConnection#getLastResponseId()}, it means that we can | |||
* {@link ApplicationConnection#getLastSeenServerSyncId()}, it means that we can | |||
* skip some quite expensive calculations because we know that the state | |||
* hasn't changed since the last time the values were calculated. | |||
*/ | |||
@@ -422,7 +422,7 @@ public abstract class AbstractOrderedLayoutConnector extends | |||
*/ | |||
private void updateInternalState() { | |||
// Avoid updating again for the same data | |||
int lastResponseId = getConnection().getLastResponseId(); | |||
int lastResponseId = getConnection().getLastSeenServerSyncId(); | |||
if (processedResponseId == lastResponseId) { | |||
return; | |||
} |
@@ -175,7 +175,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
event.getWidth(), Window.getClientWidth(), | |||
Window.getClientHeight()); | |||
if (getState().immediate || getPageState().hasResizeListeners) { | |||
getConnection().sendPendingVariableChanges(); | |||
getConnection().getServerRpcQueue().flush(); | |||
} | |||
} | |||
}); | |||
@@ -770,9 +770,12 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
} | |||
if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) { | |||
getConnection().setPushEnabled( | |||
getConnection().getMessageSender().setPushEnabled( | |||
getState().pushConfiguration.mode.isEnabled()); | |||
} | |||
if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) { | |||
getConnection().getConnectionStateHandler().configurationUpdated(); | |||
} | |||
if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) { | |||
VOverlay.setOverlayContainerLabel(getConnection(), | |||
@@ -797,13 +800,13 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
} | |||
getRpcProxy(UIServerRpc.class).poll(); | |||
// Send changes even though poll is @Delayed | |||
getConnection().sendPendingVariableChanges(); | |||
getConnection().getServerRpcQueue().flush(); | |||
} | |||
}; | |||
pollTimer.scheduleRepeating(getState().pollInterval); | |||
} else { | |||
// Ensure no more polls are sent as polling has been disabled | |||
getConnection().removePendingInvocations( | |||
getConnection().getServerRpcQueue().removeMatching( | |||
new MethodInvocation(getConnectorId(), UIServerRpc.class | |||
.getName(), "poll")); | |||
} | |||
@@ -1042,7 +1045,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector | |||
// Request a full resynchronization from the server to deal with legacy | |||
// components | |||
getConnection().repaintAll(); | |||
getConnection().getMessageSender().resynchronize(); | |||
// Immediately update state and do layout while waiting for the resync | |||
forceStateChangeRecursively(UIConnector.this); |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.client.communication; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
/** | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ServerMessageHandlerTest { | |||
@Test | |||
public void unwrapValidJson() { | |||
String payload = "{'foo': 'bar'}"; | |||
Assert.assertEquals(payload, | |||
MessageHandler.stripJSONWrapping("for(;;);[" + payload + "]")); | |||
} | |||
@Test | |||
public void unwrapUnwrappedJson() { | |||
String payload = "{'foo': 'bar'}"; | |||
Assert.assertNull(MessageHandler.stripJSONWrapping(payload)); | |||
} | |||
@Test | |||
public void unwrapNull() { | |||
Assert.assertNull(MessageHandler.stripJSONWrapping(null)); | |||
} | |||
@Test | |||
public void unwrapEmpty() { | |||
Assert.assertNull(MessageHandler.stripJSONWrapping("")); | |||
} | |||
} |
@@ -27,6 +27,8 @@ | |||
<modules> | |||
<module organisation="com.vaadin" name="vaadin-testbench" | |||
resolver="vaadin-addons" /> | |||
<module organisation="com.vaadin" name="vaadin-testbench-parent" | |||
resolver="vaadin-addons" /> | |||
<module organisation="com.vaadin" name="vaadin-testbench-core" | |||
resolver="vaadin-addons" /> | |||
<module organisation="com.vaadin" name="vaadin-testbench-api" |
@@ -353,6 +353,10 @@ public class LegacyCommunicationManager implements Serializable { | |||
res.clear(); | |||
} | |||
public boolean isEmpty() { | |||
return res.isEmpty(); | |||
} | |||
} | |||
/** |
@@ -23,6 +23,7 @@ import java.io.Serializable; | |||
import java.text.DateFormat; | |||
import java.text.DateFormatSymbols; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Calendar; | |||
import java.util.GregorianCalendar; | |||
import java.util.Locale; | |||
import java.util.logging.Logger; | |||
@@ -120,9 +121,24 @@ public class LocaleService implements Serializable { | |||
LocaleData localeData = new LocaleData(); | |||
localeData.name = locale.toString(); | |||
Calendar c = Calendar.getInstance(locale); | |||
c.set(2015, 0, 1); | |||
SimpleDateFormat shortMonthFormat = new SimpleDateFormat("MMM", locale); | |||
SimpleDateFormat longMonthFormat = new SimpleDateFormat("MMMM", locale); | |||
int monthsInYear = c.getMaximum(Calendar.MONTH) + 1; | |||
localeData.shortMonthNames = new String[monthsInYear]; | |||
localeData.monthNames = new String[monthsInYear]; | |||
for (int month = 0; month < monthsInYear; month++) { | |||
c.set(Calendar.MONTH, month); | |||
String shortMonth = shortMonthFormat.format(c.getTime()); | |||
String longMonth = longMonthFormat.format(c.getTime()); | |||
localeData.shortMonthNames[month] = shortMonth; | |||
localeData.monthNames[month] = longMonth; | |||
} | |||
final DateFormatSymbols dfs = new DateFormatSymbols(locale); | |||
localeData.shortMonthNames = dfs.getShortMonths(); | |||
localeData.monthNames = dfs.getMonths(); | |||
// Client expects 0 based indexing, DateFormatSymbols use 1 based | |||
localeData.shortDayNames = new String[7]; | |||
localeData.dayNames = new String[7]; |
@@ -165,7 +165,7 @@ public class AtmospherePushConnection implements PushConnection { | |||
} else { | |||
try { | |||
Writer writer = new StringWriter(); | |||
new UidlWriter().write(getUI(), writer, false, async); | |||
new UidlWriter().write(getUI(), writer, async); | |||
sendMessage("for(;;);[{" + writer.toString() + "}]"); | |||
} catch (Exception e) { | |||
throw new RuntimeException("Push failed", e); |
@@ -29,6 +29,7 @@ import java.util.logging.Level; | |||
import java.util.logging.Logger; | |||
import com.vaadin.server.ClientConnector; | |||
import com.vaadin.server.Constants; | |||
import com.vaadin.server.JsonCodec; | |||
import com.vaadin.server.LegacyCommunicationManager; | |||
import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException; | |||
@@ -40,6 +41,7 @@ import com.vaadin.server.VaadinService; | |||
import com.vaadin.server.VariableOwner; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.Connector; | |||
import com.vaadin.shared.Version; | |||
import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; | |||
import com.vaadin.shared.communication.MethodInvocation; | |||
import com.vaadin.shared.communication.ServerRpc; | |||
@@ -77,6 +79,8 @@ public class ServerRpcHandler implements Serializable { | |||
private final int syncId; | |||
private final JsonObject json; | |||
private final boolean resynchronize; | |||
private final int clientToServerMessageId; | |||
private String widgetsetVersion = null; | |||
public RpcRequest(String jsonString, VaadinRequest request) { | |||
json = JsonUtil.parse(jsonString); | |||
@@ -106,7 +110,19 @@ public class ServerRpcHandler implements Serializable { | |||
} else { | |||
resynchronize = false; | |||
} | |||
if (json.hasKey(ApplicationConstants.WIDGETSET_VERSION_ID)) { | |||
widgetsetVersion = json | |||
.getString(ApplicationConstants.WIDGETSET_VERSION_ID); | |||
} | |||
if (json.hasKey(ApplicationConstants.CLIENT_TO_SERVER_ID)) { | |||
clientToServerMessageId = (int) json | |||
.getNumber(ApplicationConstants.CLIENT_TO_SERVER_ID); | |||
} else { | |||
getLogger() | |||
.warning("Server message without client id received"); | |||
clientToServerMessageId = -1; | |||
} | |||
invocations = json.getArray(ApplicationConstants.RPC_INVOCATIONS); | |||
} | |||
@@ -148,6 +164,15 @@ public class ServerRpcHandler implements Serializable { | |||
return resynchronize; | |||
} | |||
/** | |||
* Gets the id of the client to server message | |||
* | |||
* @return the server message id | |||
*/ | |||
public int getClientToServerId() { | |||
return clientToServerMessageId; | |||
} | |||
/** | |||
* Gets the entire request in JSON format, as it was received from the | |||
* client. | |||
@@ -161,6 +186,17 @@ public class ServerRpcHandler implements Serializable { | |||
public JsonObject getRawJson() { | |||
return json; | |||
} | |||
/** | |||
* Gets the widget set version reported by the client | |||
* | |||
* @since 7.6 | |||
* @return The widget set version reported by the client or null if the | |||
* message did not contain a widget set version | |||
*/ | |||
public String getWidgetsetVersion() { | |||
return widgetsetVersion; | |||
} | |||
} | |||
private static final int MAX_BUFFER_SIZE = 64 * 1024; | |||
@@ -199,8 +235,43 @@ public class ServerRpcHandler implements Serializable { | |||
rpcRequest.getCsrfToken())) { | |||
throw new InvalidUIDLSecurityKeyException(""); | |||
} | |||
handleInvocations(ui, rpcRequest.getSyncId(), | |||
rpcRequest.getRpcInvocationsData()); | |||
checkWidgetsetVersion(rpcRequest.getWidgetsetVersion()); | |||
int expectedId = ui.getLastProcessedClientToServerId() + 1; | |||
if (rpcRequest.getClientToServerId() != -1 | |||
&& rpcRequest.getClientToServerId() != expectedId) { | |||
// Invalid message id, skip RPC processing but force a full | |||
// re-synchronization of the client as it might have not received | |||
// the previous response (e.g. due to a bad connection) | |||
// Must resync also for duplicate messages because the server might | |||
// have generated a response for the first message but the response | |||
// did not reach the client. When the client re-sends the message, | |||
// it would only get an empty response (because the dirty flags have | |||
// been cleared on the server) and would be out of sync | |||
ui.getSession().getCommunicationManager().repaintAll(ui); | |||
if (rpcRequest.getClientToServerId() < expectedId) { | |||
// Just a duplicate message due to a bad connection or similar | |||
// It has already been handled by the server so it is safe to | |||
// ignore | |||
getLogger().fine( | |||
"Ignoring old message from the client. Expected: " | |||
+ expectedId + ", got: " | |||
+ rpcRequest.getClientToServerId()); | |||
} else { | |||
getLogger().warning( | |||
"Unexpected message id from the client. Expected: " | |||
+ expectedId + ", got: " | |||
+ rpcRequest.getClientToServerId()); | |||
} | |||
} else { | |||
// Message id ok, process RPCs | |||
ui.setLastProcessedClientToServerId(expectedId); | |||
handleInvocations(ui, rpcRequest.getSyncId(), | |||
rpcRequest.getRpcInvocationsData()); | |||
} | |||
ui.getConnectorTracker().cleanConcurrentlyRemovedConnectorIds( | |||
rpcRequest.getSyncId()); | |||
@@ -208,6 +279,29 @@ public class ServerRpcHandler implements Serializable { | |||
if (rpcRequest.isResynchronize()) { | |||
ui.getSession().getCommunicationManager().repaintAll(ui); | |||
} | |||
} | |||
/** | |||
* Checks that the version reported by the client (widgetset) matches that | |||
* of the server. | |||
* | |||
* @param widgetsetVersion | |||
* the widget set version reported by the client or null | |||
*/ | |||
private void checkWidgetsetVersion(String widgetsetVersion) { | |||
if (widgetsetVersion == null) { | |||
// Only check when the widgetset version is reported. It is reported | |||
// in the first UIDL request (not the initial request as it is a | |||
// plain GET /) | |||
return; | |||
} | |||
if (!Version.getFullVersion().equals(widgetsetVersion)) { | |||
getLogger().warning( | |||
String.format(Constants.WIDGETSET_MISMATCH_INFO, | |||
Version.getFullVersion(), widgetsetVersion)); | |||
} | |||
} | |||
/** |
@@ -282,7 +282,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { | |||
if (session.getConfiguration().isXsrfProtectionEnabled()) { | |||
writer.write(getSecurityKeyUIDL(session)); | |||
} | |||
new UidlWriter().write(uI, writer, true, false); | |||
new UidlWriter().write(uI, writer, false); | |||
writer.write("}"); | |||
String initialUIDL = writer.toString(); |
@@ -22,7 +22,6 @@ import java.io.Writer; | |||
import java.util.logging.Level; | |||
import java.util.logging.Logger; | |||
import com.vaadin.server.Constants; | |||
import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException; | |||
import com.vaadin.server.ServletPortletHelper; | |||
import com.vaadin.server.SessionExpiredHandler; | |||
@@ -32,9 +31,7 @@ import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.server.VaadinResponse; | |||
import com.vaadin.server.VaadinService; | |||
import com.vaadin.server.VaadinSession; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.shared.JsonConstants; | |||
import com.vaadin.shared.Version; | |||
import com.vaadin.ui.UI; | |||
import elemental.json.JsonException; | |||
@@ -76,29 +73,12 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements | |||
return true; | |||
} | |||
checkWidgetsetVersion(request); | |||
// repaint requested or session has timed out and new one is created | |||
boolean repaintAll; | |||
// TODO PUSH analyzeLayouts should be | |||
// part of the message payload to make the functionality transport | |||
// agnostic | |||
// Resynchronize is sent in the payload but will still support the | |||
// parameter also for compatibility reasons | |||
repaintAll = (request | |||
.getParameter(ApplicationConstants.URL_PARAMETER_REPAINT_ALL) != null); | |||
StringWriter stringWriter = new StringWriter(); | |||
try { | |||
rpcHandler.handleRpc(uI, request.getReader(), request); | |||
if (repaintAll) { | |||
session.getCommunicationManager().repaintAll(uI); | |||
} | |||
writeUidl(request, response, uI, stringWriter, repaintAll); | |||
writeUidl(request, response, uI, stringWriter); | |||
} catch (JsonException e) { | |||
getLogger().log(Level.SEVERE, "Error writing JSON to response", e); | |||
// Refresh on client side | |||
@@ -119,28 +99,6 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements | |||
stringWriter.toString()); | |||
} | |||
/** | |||
* Checks that the version reported by the client (widgetset) matches that | |||
* of the server. | |||
* | |||
* @param request | |||
*/ | |||
private void checkWidgetsetVersion(VaadinRequest request) { | |||
String widgetsetVersion = request.getParameter("v-wsver"); | |||
if (widgetsetVersion == null) { | |||
// Only check when the widgetset version is reported. It is reported | |||
// in the first UIDL request (not the initial request as it is a | |||
// plain GET /) | |||
return; | |||
} | |||
if (!Version.getFullVersion().equals(widgetsetVersion)) { | |||
getLogger().warning( | |||
String.format(Constants.WIDGETSET_MISMATCH_INFO, | |||
Version.getFullVersion(), widgetsetVersion)); | |||
} | |||
} | |||
private void writeRefresh(VaadinRequest request, VaadinResponse response) | |||
throws IOException { | |||
String json = VaadinService.createCriticalNotificationJSON(null, null, | |||
@@ -149,10 +107,10 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements | |||
} | |||
private void writeUidl(VaadinRequest request, VaadinResponse response, | |||
UI ui, Writer writer, boolean repaintAll) throws IOException { | |||
UI ui, Writer writer) throws IOException { | |||
openJsonMessage(writer, response); | |||
new UidlWriter().write(ui, writer, repaintAll, false); | |||
new UidlWriter().write(ui, writer, false); | |||
closeJsonMessage(writer); | |||
} |
@@ -63,8 +63,6 @@ public class UidlWriter implements Serializable { | |||
* The {@link UI} whose changes to write | |||
* @param writer | |||
* The writer to use | |||
* @param repaintAll | |||
* Whether the client should re-render the whole UI. | |||
* @param analyzeLayouts | |||
* Whether detected layout problems should be logged. | |||
* @param async | |||
@@ -74,8 +72,7 @@ public class UidlWriter implements Serializable { | |||
* @throws IOException | |||
* If the writing fails. | |||
*/ | |||
public void write(UI ui, Writer writer, boolean repaintAll, boolean async) | |||
throws IOException { | |||
public void write(UI ui, Writer writer, boolean async) throws IOException { | |||
VaadinSession session = ui.getSession(); | |||
VaadinService service = session.getService(); | |||
@@ -86,6 +83,8 @@ public class UidlWriter implements Serializable { | |||
Set<ClientConnector> processedConnectors = new HashSet<ClientConnector>(); | |||
LegacyCommunicationManager manager = session.getCommunicationManager(); | |||
ClientCache clientCache = manager.getClientCache(ui); | |||
boolean repaintAll = clientCache.isEmpty(); | |||
// Paints components | |||
ConnectorTracker uiConnectorTracker = ui.getConnectorTracker(); | |||
getLogger().log(Level.FINE, "* Creating response to client"); | |||
@@ -130,7 +129,14 @@ public class UidlWriter implements Serializable { | |||
.getCurrentSyncId() : -1; | |||
writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID + "\": " | |||
+ syncId + ", "); | |||
if (repaintAll) { | |||
writer.write("\"" + ApplicationConstants.RESYNCHRONIZE_ID | |||
+ "\": true, "); | |||
} | |||
int nextClientToServerMessageId = ui | |||
.getLastProcessedClientToServerId() + 1; | |||
writer.write("\"" + ApplicationConstants.CLIENT_TO_SERVER_ID | |||
+ "\": " + nextClientToServerMessageId + ", "); | |||
writer.write("\"changes\" : "); | |||
JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer, | |||
@@ -202,7 +208,6 @@ public class UidlWriter implements Serializable { | |||
Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget | |||
.getUsedClientConnectors(); | |||
boolean typeMappingsOpen = false; | |||
ClientCache clientCache = manager.getClientCache(ui); | |||
List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>(); | |||
@@ -207,8 +207,14 @@ class PushConfigurationImpl implements PushConfiguration { | |||
@Override | |||
public Transport getTransport() { | |||
try { | |||
return Transport | |||
Transport tr = Transport | |||
.getByIdentifier(getParameter(PushConfigurationState.TRANSPORT_PARAM)); | |||
if (tr == Transport.WEBSOCKET | |||
&& getState(false).alwaysUseXhrForServerRequests) { | |||
return Transport.WEBSOCKET_XHR; | |||
} else { | |||
return tr; | |||
} | |||
} catch (IllegalArgumentException e) { | |||
return null; | |||
} | |||
@@ -223,8 +229,16 @@ class PushConfigurationImpl implements PushConfiguration { | |||
*/ | |||
@Override | |||
public void setTransport(Transport transport) { | |||
setParameter(PushConfigurationState.TRANSPORT_PARAM, | |||
transport.getIdentifier()); | |||
if (transport == Transport.WEBSOCKET_XHR) { | |||
getState().alwaysUseXhrForServerRequests = true; | |||
// Atmosphere knows only about "websocket" | |||
setParameter(PushConfigurationState.TRANSPORT_PARAM, | |||
Transport.WEBSOCKET.getIdentifier()); | |||
} else { | |||
getState().alwaysUseXhrForServerRequests = false; | |||
setParameter(PushConfigurationState.TRANSPORT_PARAM, | |||
transport.getIdentifier()); | |||
} | |||
} | |||
/* | |||
@@ -251,6 +265,10 @@ class PushConfigurationImpl implements PushConfiguration { | |||
*/ | |||
@Override | |||
public void setFallbackTransport(Transport fallbackTransport) { | |||
if (fallbackTransport == Transport.WEBSOCKET_XHR) { | |||
throw new IllegalArgumentException( | |||
"WEBSOCKET_XHR can only be used as primary transport"); | |||
} | |||
setParameter(PushConfigurationState.FALLBACK_TRANSPORT_PARAM, | |||
fallbackTransport.getIdentifier()); | |||
} |
@@ -0,0 +1,201 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.ui; | |||
import java.io.Serializable; | |||
/** | |||
* Provides method for configuring the reconnect dialog. | |||
* | |||
* @since 7.6 | |||
* @author Vaadin Ltd | |||
*/ | |||
public interface ReconnectDialogConfiguration extends Serializable { | |||
/** | |||
* Gets the text to show in the reconnect dialog when trying to re-establish | |||
* the server connection | |||
* | |||
* @return the text to show in the reconnect dialog | |||
*/ | |||
public String getDialogText(); | |||
/** | |||
* Sets the text to show in the reconnect dialog when trying to re-establish | |||
* the server connection | |||
* | |||
* @param dialogText | |||
* the text to show in the reconnect dialog | |||
*/ | |||
public void setDialogText(String dialogText); | |||
/** | |||
* Gets the text to show in the reconnect dialog after giving up trying to | |||
* reconnect ({@link #getReconnectAttempts()} reached) | |||
* | |||
* @return the text to show in the reconnect dialog after giving up | |||
*/ | |||
public String getDialogTextGaveUp(); | |||
/** | |||
* Sets the text to show in the reconnect dialog after giving up trying to | |||
* reconnect ({@link #getReconnectAttempts()} reached) | |||
* | |||
* @param dialogText | |||
* the text to show in the reconnect dialog after giving up | |||
*/ | |||
public void setDialogTextGaveUp(String dialogTextGaveUp); | |||
/** | |||
* Gets the number of times to try to reconnect to the server before giving | |||
* up | |||
* | |||
* @return the number of times to try to reconnect | |||
*/ | |||
public int getReconnectAttempts(); | |||
/** | |||
* Sets the number of times to try to reconnect to the server before giving | |||
* up | |||
* | |||
* @param reconnectAttempts | |||
* the number of times to try to reconnect | |||
*/ | |||
public void setReconnectAttempts(int reconnectAttempts); | |||
/** | |||
* Gets the interval (in milliseconds) between reconnect attempts | |||
* | |||
* @return the interval (in ms) between reconnect attempts | |||
*/ | |||
public int getReconnectInterval(); | |||
/** | |||
* Sets the interval (in milliseconds) between reconnect attempts | |||
* | |||
* @param reconnectInterval | |||
* the interval (in ms) between reconnect attempts | |||
*/ | |||
public void setReconnectInterval(int reconnectInterval); | |||
/** | |||
* Gets the timeout (in milliseconds) between noticing a loss of connection | |||
* and showing the dialog. | |||
* | |||
* @return the time to wait before showing a dialog | |||
*/ | |||
public int getDialogGracePeriod(); | |||
/** | |||
* Sets the timeout (in milliseconds) between noticing a loss of connection | |||
* and showing the dialog. | |||
* | |||
* @param dialogGracePeriod | |||
* the time to wait before showing a dialog | |||
*/ | |||
public void setDialogGracePeriod(int dialogGracePeriod); | |||
/** | |||
* Sets the modality of the dialog. | |||
* <p> | |||
* If the dialog is set to modal, it will prevent the usage of the | |||
* application while the dialog is being shown. If not modal, the user can | |||
* continue to use the application as normally and all server events will be | |||
* queued until connection has been re-established. | |||
* | |||
* @param dialogModal | |||
* true to make the dialog modal, false otherwise | |||
*/ | |||
public void setDialogModal(boolean dialogModal); | |||
/** | |||
* Checks the modality of the dialog. | |||
* <p> | |||
* | |||
* @see #setDialogModal(boolean) | |||
* @return true if the dialog is modal, false otherwise | |||
*/ | |||
public boolean isDialogModal(); | |||
} | |||
class ReconnectDialogConfigurationImpl implements ReconnectDialogConfiguration { | |||
private UI ui; | |||
public ReconnectDialogConfigurationImpl(UI ui) { | |||
this.ui = ui; | |||
} | |||
@Override | |||
public String getDialogText() { | |||
return ui.getState(false).reconnectDialogConfiguration.dialogText; | |||
} | |||
@Override | |||
public void setDialogText(String dialogText) { | |||
ui.getState().reconnectDialogConfiguration.dialogText = dialogText; | |||
} | |||
@Override | |||
public String getDialogTextGaveUp() { | |||
return ui.getState(false).reconnectDialogConfiguration.dialogTextGaveUp; | |||
} | |||
@Override | |||
public void setDialogTextGaveUp(String dialogTextGaveUp) { | |||
ui.getState().reconnectDialogConfiguration.dialogTextGaveUp = dialogTextGaveUp; | |||
} | |||
@Override | |||
public int getReconnectAttempts() { | |||
return ui.getState(false).reconnectDialogConfiguration.reconnectAttempts; | |||
} | |||
@Override | |||
public void setReconnectAttempts(int reconnectAttempts) { | |||
ui.getState().reconnectDialogConfiguration.reconnectAttempts = reconnectAttempts; | |||
} | |||
@Override | |||
public int getReconnectInterval() { | |||
return ui.getState(false).reconnectDialogConfiguration.reconnectInterval; | |||
} | |||
@Override | |||
public void setReconnectInterval(int reconnectInterval) { | |||
ui.getState().reconnectDialogConfiguration.reconnectInterval = reconnectInterval; | |||
} | |||
@Override | |||
public int getDialogGracePeriod() { | |||
return ui.getState(false).reconnectDialogConfiguration.dialogGracePeriod; | |||
} | |||
@Override | |||
public void setDialogGracePeriod(int dialogGracePeriod) { | |||
ui.getState().reconnectDialogConfiguration.dialogGracePeriod = dialogGracePeriod; | |||
} | |||
@Override | |||
public boolean isDialogModal() { | |||
return ui.getState(false).reconnectDialogConfiguration.dialogModal; | |||
} | |||
@Override | |||
public void setDialogModal(boolean dialogModal) { | |||
ui.getState().reconnectDialogConfiguration.dialogModal = dialogModal; | |||
} | |||
} |
@@ -255,10 +255,18 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
this); | |||
private PushConfiguration pushConfiguration = new PushConfigurationImpl( | |||
this); | |||
private ReconnectDialogConfiguration reconnectDialogConfiguration = new ReconnectDialogConfigurationImpl( | |||
this); | |||
private NotificationConfiguration notificationConfiguration = new NotificationConfigurationImpl( | |||
this); | |||
/** | |||
* Tracks which message from the client should come next. First message from | |||
* the client has id 0. | |||
*/ | |||
private int lastProcessedClientToServerId = -1; | |||
/** | |||
* Creates a new empty UI without a caption. The content of the UI must be | |||
* set by calling {@link #setContent(Component)} before using the UI. | |||
@@ -1639,6 +1647,16 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
return pushConfiguration; | |||
} | |||
/** | |||
* Retrieves the object used for configuring the reconnect dialog. | |||
* | |||
* @since 7.6 | |||
* @return The instance used for reconnect dialog configuration | |||
*/ | |||
public ReconnectDialogConfiguration getReconnectDialogConfiguration() { | |||
return reconnectDialogConfiguration; | |||
} | |||
/** | |||
* Get the label that is added to the container element, where tooltip, | |||
* notification and dialogs are added to. | |||
@@ -1691,4 +1709,31 @@ public abstract class UI extends AbstractSingleComponentContainer implements | |||
public String getEmbedId() { | |||
return embedId; | |||
} | |||
/** | |||
* Gets the last processed server message id. | |||
* | |||
* Used internally for communication tracking. | |||
* | |||
* @return lastProcessedServerMessageId the id of the last processed server | |||
* message | |||
* @since 7.6 | |||
*/ | |||
public int getLastProcessedClientToServerId() { | |||
return lastProcessedClientToServerId; | |||
} | |||
/** | |||
* Sets the last processed server message id. | |||
* | |||
* Used internally for communication tracking. | |||
* | |||
* @param lastProcessedServerMessageId | |||
* the id of the last processed server message | |||
* @since 7.6 | |||
*/ | |||
public void setLastProcessedClientToServerId( | |||
int lastProcessedClientToServerId) { | |||
this.lastProcessedClientToServerId = lastProcessedClientToServerId; | |||
} | |||
} |
@@ -121,6 +121,11 @@ public class Upload extends AbstractComponent implements Component.Focusable, | |||
public void change(String filename) { | |||
fireEvent(new ChangeEvent(Upload.this, filename)); | |||
} | |||
@Override | |||
public void poll() { | |||
// Nothing to do, called only to visit the server | |||
} | |||
}); | |||
} | |||
@@ -40,6 +40,12 @@ public class PushConfigurationTransportTest { | |||
ui.getPushConfiguration().setTransport(transport); | |||
Assert.assertEquals(ui.getPushConfiguration().getTransport(), | |||
transport); | |||
if (transport == Transport.WEBSOCKET_XHR) { | |||
Assert.assertTrue(ui.getState().pushConfiguration.alwaysUseXhrForServerRequests); | |||
} else { | |||
Assert.assertFalse(ui.getState().pushConfiguration.alwaysUseXhrForServerRequests); | |||
} | |||
} | |||
} |
@@ -132,6 +132,12 @@ public class ApplicationConstants implements Serializable { | |||
*/ | |||
public static final String SERVER_SYNC_ID = "syncId"; | |||
/** | |||
* The name of the parameter used to transmit the id of the client to server | |||
* messages. | |||
*/ | |||
public static final String CLIENT_TO_SERVER_ID = "clientId"; | |||
/** | |||
* Default value to use in case the security protection is disabled. | |||
*/ | |||
@@ -142,4 +148,10 @@ public class ApplicationConstants implements Serializable { | |||
*/ | |||
public static final String RESYNCHRONIZE_ID = "resynchronize"; | |||
/** | |||
* The name of the parameter used for sending the widget set version to the | |||
* server | |||
*/ | |||
public static final String WIDGETSET_VERSION_ID = "wsver"; | |||
} |
@@ -27,6 +27,12 @@ public enum Transport { | |||
* Websockets | |||
*/ | |||
WEBSOCKET("websocket"), | |||
/** | |||
* Websockets for server to client, XHR for client to server | |||
* | |||
* @since 7.6 | |||
*/ | |||
WEBSOCKET_XHR("websocket-xhr"), | |||
/** | |||
* HTTP streaming | |||
* |
@@ -72,6 +72,7 @@ public class UIState extends TabIndexState { | |||
* @since 7.3 | |||
*/ | |||
public String theme; | |||
public ReconnectDialogConfigurationState reconnectDialogConfiguration = new ReconnectDialogConfigurationState(); | |||
{ | |||
primaryStyleName = "v-ui"; | |||
// Default is 1 for legacy reasons | |||
@@ -113,6 +114,7 @@ public class UIState extends TabIndexState { | |||
public static final String TRANSPORT_PARAM = "transport"; | |||
public static final String FALLBACK_TRANSPORT_PARAM = "fallbackTransport"; | |||
public boolean alwaysUseXhrForServerRequests = false; | |||
public PushMode mode = PushMode.DISABLED; | |||
public Map<String, String> parameters = new HashMap<String, String>(); | |||
{ | |||
@@ -123,6 +125,16 @@ public class UIState extends TabIndexState { | |||
} | |||
} | |||
public static class ReconnectDialogConfigurationState implements | |||
Serializable { | |||
public String dialogText = "Server connection lost, trying to reconnect..."; | |||
public String dialogTextGaveUp = "Server connection lost."; | |||
public int reconnectAttempts = 10000; | |||
public int reconnectInterval = 5000; | |||
public int dialogGracePeriod = 1000; | |||
public boolean dialogModal = true; | |||
} | |||
public static class LocaleServiceState implements Serializable { | |||
public List<LocaleData> localeData = new ArrayList<LocaleData>(); | |||
} |
@@ -27,4 +27,12 @@ public interface UploadServerRpc extends ServerRpc { | |||
*/ | |||
void change(String filename); | |||
/** | |||
* Called to poll the server to see if any changes have been made e.g. when | |||
* starting upload | |||
* | |||
* @since | |||
*/ | |||
void poll(); | |||
} |
@@ -230,6 +230,13 @@ | |||
<param name="target-server" value="wildfly9" /> | |||
</antcall> | |||
</target> | |||
<target name="integration-test-wildfly9-nginx"> | |||
<antcall target="run-generic-integration-test"> | |||
<param name="startDelay" value="10" /> | |||
<param name="target-server" value="wildfly9-nginx" /> | |||
<param name="target-port" value="80" /> | |||
</antcall> | |||
</target> | |||
<target name="integration-test-glassfish3"> | |||
<antcall target="run-generic-integration-test"> | |||
<param name="startDelay" value="10" /> |
@@ -78,15 +78,19 @@ | |||
<exclude org="org.eclipse.jetty.orbit"></exclude> | |||
</dependency> | |||
<dependency org="org.eclipse.jetty" name="jetty-websocket" | |||
rev="&jetty.version;" conf="ide, jetty-run->default"> | |||
rev="&jetty.version;" conf="ide, build-provided, jetty-run->default"> | |||
<exclude org="org.eclipse.jetty.orbit"></exclude> | |||
</dependency> | |||
<dependency org="org.eclipse.jetty" name="jetty-webapp" | |||
rev="&jetty.version;" conf="ide, build-provided, jetty-run->default"> | |||
<exclude org="org.eclipse.jetty.orbit"></exclude> | |||
</dependency> | |||
<dependency org="org.eclipse.jetty" name="jetty-util" | |||
rev="&jetty.version;" conf="ide, build-provided, jetty-run->default"> | |||
<exclude org="org.eclipse.jetty.orbit"></exclude> | |||
</dependency> | |||
<dependency org="org.mortbay.jetty" name="jetty-runner" | |||
rev="&jetty.version;" conf="ide, jetty-run->default"> | |||
rev="&jetty.version;" conf="ide, build-provided, jetty-run->default"> | |||
<exclude org="org.eclipse.jetty.orbit"></exclude> | |||
</dependency> | |||
@@ -104,7 +108,7 @@ | |||
<dependency org="org.hsqldb" name="hsqldb" rev="2.2.6" | |||
conf="build,ide -> default" /> | |||
<dependency org="com.vaadin" name="vaadin-testbench" | |||
rev="4.0.2" conf="build-provided,ide -> default" /> | |||
rev="4.0.3" conf="build-provided,ide -> default" /> | |||
<!-- This should be removed once tests have been updated to use lang3 --> | |||
<dependency org="commons-lang" name="commons-lang" | |||
rev="2.6" conf="build,ide -> default" /> | |||
@@ -114,7 +118,6 @@ | |||
<dependency org="com.vaadin" name="vaadin-buildhelpers" | |||
rev="${vaadin.version}" conf="compile-theme->build" /> | |||
<dependency org="org.eclipse.jgit" name="org.eclipse.jgit" | |||
rev="3.5.1.201410131835-r" conf="ide,build->default"> | |||
<exclude org="org.apache.httpcomponents"></exclude> |
@@ -0,0 +1,151 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.application; | |||
import java.io.IOException; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import com.vaadin.server.DeploymentConfiguration; | |||
import com.vaadin.server.RequestHandler; | |||
import com.vaadin.server.ServiceException; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.server.VaadinResponse; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.server.VaadinServletService; | |||
import com.vaadin.server.VaadinSession; | |||
import com.vaadin.server.communication.HeartbeatHandler; | |||
import com.vaadin.server.communication.UidlRequestHandler; | |||
import com.vaadin.ui.UI; | |||
public class CommErrorEmulatorServlet extends VaadinServlet { | |||
private Map<UI, Integer> uidlResponseCode = Collections | |||
.synchronizedMap(new HashMap<UI, Integer>()); | |||
private Map<UI, Integer> heartbeatResponseCode = Collections | |||
.synchronizedMap(new HashMap<UI, Integer>()); | |||
private final CommErrorUIDLRequestHandler uidlHandler = new CommErrorUIDLRequestHandler(); | |||
private final CommErrorHeartbeatHandler heartbeatHandler = new CommErrorHeartbeatHandler(); | |||
public class CommErrorUIDLRequestHandler extends UidlRequestHandler { | |||
@Override | |||
public boolean synchronizedHandleRequest(VaadinSession session, | |||
VaadinRequest request, VaadinResponse response) | |||
throws IOException { | |||
UI ui = session.getService().findUI(request); | |||
if (ui != null && uidlResponseCode.containsKey(ui)) { | |||
response.sendError(uidlResponseCode.get(ui), "Error set in UI"); | |||
return true; | |||
} | |||
return super.synchronizedHandleRequest(session, request, response); | |||
} | |||
} | |||
public class CommErrorHeartbeatHandler extends HeartbeatHandler { | |||
@Override | |||
public boolean synchronizedHandleRequest(VaadinSession session, | |||
VaadinRequest request, VaadinResponse response) | |||
throws IOException { | |||
UI ui = session.getService().findUI(request); | |||
if (ui != null && heartbeatResponseCode.containsKey(ui)) { | |||
response.sendError(heartbeatResponseCode.get(ui), | |||
"Error set in UI"); | |||
return true; | |||
} | |||
return super.synchronizedHandleRequest(session, request, response); | |||
} | |||
} | |||
public class CommErrorEmulatorService extends VaadinServletService { | |||
public CommErrorEmulatorService(VaadinServlet servlet, | |||
DeploymentConfiguration deploymentConfiguration) | |||
throws ServiceException { | |||
super(servlet, deploymentConfiguration); | |||
} | |||
@Override | |||
protected List<RequestHandler> createRequestHandlers() | |||
throws ServiceException { | |||
List<RequestHandler> handlers = super.createRequestHandlers(); | |||
handlers.add(uidlHandler); | |||
handlers.add(heartbeatHandler); | |||
return handlers; | |||
} | |||
} | |||
@Override | |||
protected VaadinServletService createServletService( | |||
DeploymentConfiguration deploymentConfiguration) | |||
throws ServiceException { | |||
CommErrorEmulatorService s = new CommErrorEmulatorService(this, | |||
deploymentConfiguration); | |||
s.init(); | |||
return s; | |||
} | |||
public void setUIDLResponseCode(final UI ui, int responseCode, | |||
final int delay) { | |||
uidlResponseCode.put(ui, responseCode); | |||
System.out.println("Responding with " + responseCode | |||
+ " to UIDL requests for " + ui + " for the next " + delay | |||
+ "s"); | |||
new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
try { | |||
Thread.sleep(delay * 1000); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
System.out.println("Handing UIDL requests normally again"); | |||
uidlResponseCode.remove(ui); | |||
} | |||
}).start(); | |||
} | |||
public void setHeartbeatResponseCode(final UI ui, int responseCode, | |||
final int delay) { | |||
heartbeatResponseCode.put(ui, responseCode); | |||
System.out.println("Responding with " + responseCode | |||
+ " to heartbeat requests for " + ui + " for the next " + delay | |||
+ "s"); | |||
new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
try { | |||
Thread.sleep(delay * 1000); | |||
} catch (InterruptedException e) { | |||
e.printStackTrace(); | |||
} | |||
System.out.println("Handing heartbeat requests normally again"); | |||
heartbeatResponseCode.remove(ui); | |||
} | |||
}).start(); | |||
} | |||
} |
@@ -0,0 +1,270 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.application; | |||
import com.vaadin.annotations.Theme; | |||
import com.vaadin.data.Property.ValueChangeEvent; | |||
import com.vaadin.data.Property.ValueChangeListener; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.server.VaadinServlet; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
import com.vaadin.ui.CheckBox; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.Panel; | |||
import com.vaadin.ui.TextField; | |||
import com.vaadin.ui.VerticalLayout; | |||
/** | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
@Theme("valo") | |||
public class CommErrorEmulatorUI extends AbstractTestUIWithLog { | |||
private static class Response { | |||
private Integer code; | |||
private Integer time; | |||
/** | |||
* @param code | |||
* @param time | |||
*/ | |||
public Response(Integer code, Integer time) { | |||
super(); | |||
this.code = code; | |||
this.time = time; | |||
} | |||
} | |||
private Response uidlResponse = new Response(503, 10); | |||
private Response heartbeatResponse = new Response(200, 10); | |||
// Server exceptions will occur in this test as we are writing the response | |||
// here and not letting the servlet write it | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
String transport = request.getParameter("transport"); | |||
if ("websocket".equalsIgnoreCase(transport)) { | |||
log("Using websocket"); | |||
} else if ("websocket-xhr".equalsIgnoreCase(transport)) { | |||
log("Using websocket for push only"); | |||
} else if ("long-polling".equalsIgnoreCase(transport)) { | |||
log("Using long-polling"); | |||
} else { | |||
log("Using XHR"); | |||
} | |||
getLayout().setSpacing(true); | |||
addComponent(createConfigPanel()); | |||
addComponent(createServerConfigPanel()); | |||
addComponent(new Button("Say hello", new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
log("Hello"); | |||
} | |||
})); | |||
} | |||
/** | |||
* @since | |||
* @return | |||
*/ | |||
private Component createServerConfigPanel() { | |||
Panel p = new Panel("Server config (NOTE: affects all users)"); | |||
VerticalLayout vl = new VerticalLayout(); | |||
vl.setSpacing(true); | |||
vl.setMargin(true); | |||
p.setContent(vl); | |||
vl.addComponent(createTemporaryResponseCodeSetters("UIDL", uidlResponse)); | |||
vl.addComponent(createTemporaryResponseCodeSetters("Heartbeat", | |||
heartbeatResponse)); | |||
vl.addComponent(new Button("Activate", new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
if (uidlResponse.code != null && uidlResponse.code != 200) { | |||
getServlet().setUIDLResponseCode(CommErrorEmulatorUI.this, | |||
uidlResponse.code, uidlResponse.time); | |||
log("Responding with " + uidlResponse.code | |||
+ " to UIDL requests for " + uidlResponse.time | |||
+ "s"); | |||
} | |||
if (heartbeatResponse.code != null | |||
&& heartbeatResponse.code != 200) { | |||
getServlet().setHeartbeatResponseCode( | |||
CommErrorEmulatorUI.this, heartbeatResponse.code, | |||
heartbeatResponse.time); | |||
log("Responding with " + heartbeatResponse.code | |||
+ " to heartbeat requests for " | |||
+ heartbeatResponse.time + "s"); | |||
} | |||
} | |||
})); | |||
return p; | |||
} | |||
private Component createConfigPanel() { | |||
Panel p = new Panel("Reconnect dialog configuration"); | |||
p.setSizeUndefined(); | |||
final TextField reconnectDialogMessage = new TextField( | |||
"Reconnect message"); | |||
reconnectDialogMessage.setWidth("50em"); | |||
reconnectDialogMessage.setValue(getReconnectDialogConfiguration() | |||
.getDialogText()); | |||
reconnectDialogMessage | |||
.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setDialogText( | |||
reconnectDialogMessage.getValue()); | |||
} | |||
}); | |||
final TextField reconnectDialogGaveUpMessage = new TextField( | |||
"Reconnect gave up message"); | |||
reconnectDialogGaveUpMessage.setWidth("50em"); | |||
reconnectDialogGaveUpMessage.setValue(getReconnectDialogConfiguration() | |||
.getDialogTextGaveUp()); | |||
reconnectDialogGaveUpMessage | |||
.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setDialogTextGaveUp( | |||
reconnectDialogGaveUpMessage.getValue()); | |||
} | |||
}); | |||
final TextField reconnectDialogReconnectAttempts = new TextField( | |||
"Reconnect attempts"); | |||
reconnectDialogReconnectAttempts.setConverter(Integer.class); | |||
reconnectDialogReconnectAttempts | |||
.setConvertedValue(getReconnectDialogConfiguration() | |||
.getReconnectAttempts()); | |||
reconnectDialogReconnectAttempts | |||
.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setReconnectAttempts( | |||
(Integer) reconnectDialogReconnectAttempts | |||
.getConvertedValue()); | |||
} | |||
}); | |||
final TextField reconnectDialogReconnectInterval = new TextField( | |||
"Reconnect interval (ms)"); | |||
reconnectDialogReconnectInterval.setConverter(Integer.class); | |||
reconnectDialogReconnectInterval | |||
.setConvertedValue(getReconnectDialogConfiguration() | |||
.getReconnectInterval()); | |||
reconnectDialogReconnectInterval | |||
.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setReconnectInterval( | |||
(Integer) reconnectDialogReconnectInterval | |||
.getConvertedValue()); | |||
} | |||
}); | |||
final TextField reconnectDialogGracePeriod = new TextField( | |||
"Reconnect dialog grace period (ms)"); | |||
reconnectDialogGracePeriod.setConverter(Integer.class); | |||
reconnectDialogGracePeriod | |||
.setConvertedValue(getReconnectDialogConfiguration() | |||
.getDialogGracePeriod()); | |||
reconnectDialogGracePeriod | |||
.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setDialogGracePeriod( | |||
(Integer) reconnectDialogGracePeriod | |||
.getConvertedValue()); | |||
} | |||
}); | |||
final CheckBox reconnectDialogModal = new CheckBox( | |||
"Reconnect dialog modality"); | |||
reconnectDialogModal.setValue(getReconnectDialogConfiguration() | |||
.isDialogModal()); | |||
reconnectDialogModal.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
getReconnectDialogConfiguration().setDialogModal( | |||
reconnectDialogModal.getValue()); | |||
} | |||
}); | |||
VerticalLayout vl = new VerticalLayout(); | |||
vl.setMargin(true); | |||
vl.setSpacing(true); | |||
p.setContent(vl); | |||
vl.addComponents(reconnectDialogMessage, reconnectDialogGaveUpMessage, | |||
reconnectDialogGracePeriod, reconnectDialogModal, | |||
reconnectDialogReconnectAttempts, | |||
reconnectDialogReconnectInterval); | |||
return p; | |||
} | |||
private Component createTemporaryResponseCodeSetters(String type, | |||
final Response response) { | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.setSpacing(true); | |||
hl.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT); | |||
Label l1 = new Label("Respond to " + type + " requests with code"); | |||
final TextField responseCode = new TextField(null, "" + response.code); | |||
responseCode.setConverter(Integer.class); | |||
responseCode.setWidth("5em"); | |||
Label l2 = new Label("for the following"); | |||
final TextField timeField = new TextField(null, "" + response.time); | |||
timeField.setConverter(Integer.class); | |||
timeField.setWidth("5em"); | |||
Label l3 = new Label("seconds"); | |||
responseCode.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
Integer code = (Integer) responseCode.getConvertedValue(); | |||
response.code = code; | |||
} | |||
}); | |||
timeField.addValueChangeListener(new ValueChangeListener() { | |||
@Override | |||
public void valueChange(ValueChangeEvent event) { | |||
Integer time = (Integer) timeField.getConvertedValue(); | |||
response.time = time; | |||
} | |||
}); | |||
hl.addComponents(l1, responseCode, l2, timeField, l3); | |||
return hl; | |||
} | |||
protected CommErrorEmulatorServlet getServlet() { | |||
return (CommErrorEmulatorServlet) VaadinServlet.getCurrent(); | |||
} | |||
} |
@@ -22,48 +22,7 @@ import com.vaadin.testbench.elements.CheckBoxElement; | |||
import com.vaadin.testbench.elements.NotificationElement; | |||
import com.vaadin.tests.tb3.MultiBrowserThemeTest; | |||
public abstract class CriticalNotificationsTestBase extends | |||
MultiBrowserThemeTest { | |||
public static class ValoCriticalNotificationsTest extends | |||
CriticalNotificationsTestBase { | |||
@Override | |||
protected String getTheme() { | |||
return "valo"; | |||
} | |||
} | |||
public static class ReindeerCriticalNotificationsTest extends | |||
CriticalNotificationsTestBase { | |||
@Override | |||
protected String getTheme() { | |||
return "reindeer"; | |||
} | |||
} | |||
public static class RunoCriticalNotificationsTest extends | |||
CriticalNotificationsTestBase { | |||
@Override | |||
protected String getTheme() { | |||
return "runo"; | |||
} | |||
} | |||
public static class ChameleonCriticalNotificationsTest extends | |||
CriticalNotificationsTestBase { | |||
@Override | |||
protected String getTheme() { | |||
return "chameleon"; | |||
} | |||
} | |||
public static class BaseCriticalNotificationsTest extends | |||
CriticalNotificationsTestBase { | |||
@Override | |||
protected String getTheme() { | |||
return "base"; | |||
} | |||
} | |||
public class CriticalNotificationsTest extends MultiBrowserThemeTest { | |||
@Test | |||
public void internalError() throws Exception { |
@@ -0,0 +1,101 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.application; | |||
import java.awt.image.BufferedImage; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import javax.imageio.ImageIO; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.OutputType; | |||
import org.openqa.selenium.TakesScreenshot; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.parallel.BrowserUtil; | |||
import com.vaadin.testbench.parallel.TestCategory; | |||
import com.vaadin.tests.tb3.CustomTestBenchCommandExecutor; | |||
import com.vaadin.tests.tb3.MultiBrowserThemeTestWithProxy; | |||
@TestCategory("") | |||
public class ReconnectDialogThemeTest extends MultiBrowserThemeTestWithProxy { | |||
static By reconnectDialogBy = By.className("v-reconnect-dialog"); | |||
@Test | |||
public void reconnectDialogTheme() throws IOException { | |||
openTestURL(); | |||
ButtonElement helloButton = $(ButtonElement.class).caption("Say hello") | |||
.first(); | |||
helloButton.click(); | |||
Assert.assertEquals("1. Hello from the server", getLogRow(0)); | |||
disconnectProxy(); | |||
helloButton.click(); | |||
testBench().disableWaitForVaadin(); | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
boolean present = isElementPresent(reconnectDialogBy); | |||
return present; | |||
} | |||
}); | |||
WebElement dialog = findElement(reconnectDialogBy); | |||
WebElement spinner = dialog.findElement(By.className("spinner")); | |||
// Hide spinner to make screenshot stable | |||
executeScript("arguments[0].style.visibility='hidden';", spinner); | |||
compareScreen("onscreen-without-spinner"); | |||
// Show spinner and make sure it is shown by comparing to the screenshot | |||
// without a spinner | |||
executeScript("arguments[0].style.visibility='visible';", spinner); | |||
BufferedImage fullScreen = ImageIO.read(new ByteArrayInputStream( | |||
((TakesScreenshot) getDriver()) | |||
.getScreenshotAs(OutputType.BYTES))); | |||
BufferedImage spinnerImage = CustomTestBenchCommandExecutor | |||
.cropToElement(spinner, fullScreen, | |||
BrowserUtil.isIE8(getDesiredCapabilities())); | |||
assertHasManyColors("Spinner is not shown", spinnerImage); | |||
} | |||
private void assertHasManyColors(String message, BufferedImage spinnerImage) { | |||
int backgroundColor = spinnerImage.getRGB(0, 0); | |||
for (int x = 0; x < spinnerImage.getWidth(); x++) { | |||
for (int y = 0; y < spinnerImage.getHeight(); y++) { | |||
if (Math.abs(spinnerImage.getRGB(x, y) - backgroundColor) > 50) { | |||
return; | |||
} | |||
} | |||
} | |||
Assert.fail(message); | |||
} | |||
@Override | |||
protected Class<?> getUIClass() { | |||
return ReconnectDialogUI.class; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.application; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.Button.ClickEvent; | |||
import com.vaadin.ui.Button.ClickListener; | |||
public class ReconnectDialogUI extends AbstractTestUIWithLog { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
if (request.getParameter("reconnectAttempts") != null) { | |||
getReconnectDialogConfiguration() | |||
.setReconnectAttempts( | |||
Integer.parseInt(request | |||
.getParameter("reconnectAttempts"))); | |||
} | |||
Button b = new Button("Say hello"); | |||
b.addClickListener(new ClickListener() { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
log("Hello from the server"); | |||
} | |||
}); | |||
addComponent(b); | |||
} | |||
} |
@@ -0,0 +1,82 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.application; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.jcraft.jsch.JSchException; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTestWithProxy; | |||
public class ReconnectDialogUITest extends MultiBrowserTestWithProxy { | |||
@Test | |||
public void reconnectDialogShownAndDisappears() throws JSchException { | |||
openTestURL(); | |||
getButton().click(); | |||
Assert.assertEquals("1. Hello from the server", getLogRow(0)); | |||
disconnectProxy(); | |||
getButton().click(); | |||
waitForReconnectDialogWithText("Server connection lost, trying to reconnect..."); | |||
connectProxy(); | |||
waitForReconnectDialogToDisappear(); | |||
Assert.assertEquals("2. Hello from the server", getLogRow(0)); | |||
} | |||
@Test | |||
public void gaveUpMessageShown() { | |||
openTestURL("reconnectAttempts=3"); | |||
getButton().click(); | |||
Assert.assertEquals("1. Hello from the server", getLogRow(0)); | |||
disconnectProxy(); | |||
getButton().click(); | |||
waitForReconnectDialogWithText("Server connection lost."); | |||
} | |||
private void waitForReconnectDialogWithText(final String text) { | |||
waitForReconnectDialogPresent(); | |||
final WebElement reconnectDialog = findElement(ReconnectDialogThemeTest.reconnectDialogBy); | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
return reconnectDialog.findElement(By.className("text")) | |||
.getText().equals(text); | |||
} | |||
}, 10); | |||
} | |||
private void waitForReconnectDialogToDisappear() { | |||
waitForElementNotPresent(ReconnectDialogThemeTest.reconnectDialogBy); | |||
} | |||
private void waitForReconnectDialogPresent() { | |||
waitForElementPresent(ReconnectDialogThemeTest.reconnectDialogBy); | |||
} | |||
private WebElement getButton() { | |||
return $(ButtonElement.class).first(); | |||
} | |||
} |
@@ -124,6 +124,8 @@ public abstract class AbstractTestUI extends UI { | |||
config.setPushMode(PushMode.DISABLED); | |||
} else if ("websocket".equals(transport)) { | |||
enablePush(Transport.WEBSOCKET); | |||
} else if ("websocket-xhr".equals(transport)) { | |||
enablePush(Transport.WEBSOCKET_XHR); | |||
} else if ("streaming".equals(transport)) { | |||
enablePush(Transport.STREAMING); | |||
} else if ("long-polling".equals(transport)) { |
@@ -36,7 +36,7 @@ public class GridThemeChangeTest extends MultiBrowserTest { | |||
@Test | |||
public void testThemeChange() { | |||
openTestURL(); | |||
openTestURL("debug"); | |||
GridElement grid = $(GridElement.class).first(); | |||
@@ -44,6 +44,7 @@ public class GridThemeChangeTest extends MultiBrowserTest { | |||
grid.getCell(0, 0).click(); | |||
grid = $(GridElement.class).first(); | |||
int valoHeight = grid.getRow(0).getSize().getHeight(); | |||
Assert.assertTrue( |
@@ -376,9 +376,10 @@ public class GridSortingTest extends GridBasicFeaturesTest { | |||
} | |||
private void assertLastSortIsUserOriginated(boolean isUserOriginated) { | |||
// Find a message in the log | |||
List<WebElement> userOriginatedMessages = getDriver() | |||
.findElements( | |||
By.xpath("//*[contains(text(),'SortOrderChangeEvent: isUserOriginated')]")); | |||
By.xpath("//div[@id='Log']//*[contains(text(),'SortOrderChangeEvent: isUserOriginated')]")); | |||
Collections.sort(userOriginatedMessages, new Comparator<WebElement>() { | |||
@Override |
@@ -16,8 +16,13 @@ | |||
package com.vaadin.tests.integration; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized.Parameters; | |||
import com.vaadin.testbench.elements.TableElement; | |||
@@ -27,9 +32,12 @@ import com.vaadin.testbench.elements.TableElement; | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
@RunWith(ParameterizedTB3Runner.class) | |||
public abstract class AbstractServletIntegrationTest extends | |||
AbstractIntegrationTest { | |||
private String contextPath = "/demo"; | |||
@Test | |||
public void runTest() throws IOException, AssertionError { | |||
openTestURL(); | |||
@@ -40,7 +48,29 @@ public abstract class AbstractServletIntegrationTest extends | |||
@Override | |||
protected String getDeploymentPath(Class<?> uiClass) { | |||
return "/demo" + super.getDeploymentPath(uiClass); | |||
return contextPath + super.getDeploymentPath(uiClass); | |||
} | |||
public void setContextPath(String contextPath) { | |||
this.contextPath = contextPath; | |||
} | |||
@Parameters | |||
public static Collection<String> getContextPaths() { | |||
if (getServerName().equals("wildfly9-nginx")) { | |||
ArrayList<String> paths = new ArrayList<String>(); | |||
paths.add("/buffering/demo"); | |||
paths.add("/nonbuffering/demo"); | |||
paths.add("/buffering-timeout/demo"); | |||
paths.add("/nonbuffering-timeout/demo"); | |||
return paths; | |||
} else { | |||
return Collections.emptyList(); | |||
} | |||
} | |||
protected static String getServerName() { | |||
return System.getProperty("server-name"); | |||
} | |||
} |
@@ -0,0 +1,107 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.integration; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
import com.vaadin.testbench.parallel.Browser; | |||
import com.vaadin.tests.push.BasicPushLongPolling; | |||
import com.vaadin.tests.push.BasicPushTest; | |||
import com.vaadin.tests.tb3.IncludeIfProperty; | |||
@IncludeIfProperty(property = "server-name", value = "wildfly9-nginx") | |||
public class LongPollingProxyServerTest extends AbstractIntegrationTest { | |||
@Override | |||
protected Class<?> getUIClass() { | |||
return BasicPushLongPolling.class; | |||
} | |||
@Test | |||
public void bufferingTimeoutBasicPush() throws Exception { | |||
basicPush("buffering-timeout"); | |||
} | |||
@Test | |||
public void nonbufferingTimeoutBasicPush() throws Exception { | |||
basicPush("nonbuffering-timeout"); | |||
} | |||
@Test | |||
public void bufferingBasicPush() throws Exception { | |||
basicPush("buffering"); | |||
} | |||
@Test | |||
public void nonbufferingBasicPush() throws Exception { | |||
basicPush("nonbuffering"); | |||
} | |||
@Test | |||
public void bufferingTimeoutActionAfterFirstTimeout() throws Exception { | |||
actionAfterFirstTimeout("buffering-timeout"); | |||
} | |||
@Test | |||
public void nonbufferingTimeoutActionAfterFirstTimeout() throws Exception { | |||
actionAfterFirstTimeout("nonbuffering-timeout"); | |||
} | |||
private String getUrl(String bufferingOrNot) { | |||
return getBaseURL() + "/" + bufferingOrNot + "/demo" | |||
+ getDeploymentPath(); | |||
} | |||
private void actionAfterFirstTimeout(String bufferingOrNot) | |||
throws Exception { | |||
String url = getUrl(bufferingOrNot); | |||
getDriver().get(url); | |||
// The wildfly9-nginx server has a configured timeout of 10s for | |||
// *-timeout urls | |||
Thread.sleep(15000); | |||
Assert.assertEquals(0, BasicPushTest.getClientCounter(this)); | |||
BasicPushTest.getIncrementButton(this).click(); | |||
Assert.assertEquals(1, BasicPushTest.getClientCounter(this)); | |||
} | |||
private void basicPush(String bufferingOrNot) throws Exception { | |||
String url = getUrl(bufferingOrNot); | |||
getDriver().get(url); | |||
Assert.assertEquals(0, BasicPushTest.getServerCounter(this)); | |||
BasicPushTest.getServerCounterStartButton(this).click(); | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
return BasicPushTest | |||
.getServerCounter(LongPollingProxyServerTest.this) > 1; | |||
} | |||
}); | |||
} | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
return Collections.singletonList(Browser.PHANTOMJS | |||
.getDesiredCapabilities()); | |||
} | |||
} |
@@ -0,0 +1,170 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.integration; | |||
import java.lang.reflect.Method; | |||
import java.lang.reflect.Modifier; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.LinkedHashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.junit.runners.Parameterized.Parameters; | |||
import org.junit.runners.model.FrameworkMethod; | |||
import org.junit.runners.model.InitializationError; | |||
import com.vaadin.tests.tb3.TB3Runner; | |||
/** | |||
* TestBench test runner which supports static @Parameters annotated methods | |||
* providing parameters for the corresponding setter. | |||
* <p> | |||
* {@code @Parameters public static Collection<String> getThemes() } creates one | |||
* permutation for each value returned by {@code getThemes()}. The value is | |||
* automatically assigned to the test instance using {@code setTheme(String)} | |||
* before invoking the test method | |||
* | |||
* @author Vaadin Ltd | |||
*/ | |||
public class ParameterizedTB3Runner extends TB3Runner { | |||
public ParameterizedTB3Runner(Class<?> klass) throws InitializationError { | |||
super(klass); | |||
} | |||
@Override | |||
protected List<FrameworkMethod> computeTestMethods() { | |||
List<FrameworkMethod> methods = super.computeTestMethods(); | |||
Map<Method, Collection<String>> parameters = new LinkedHashMap<Method, Collection<String>>(); | |||
// Find all @Parameters methods and invoke them to find out permutations | |||
for (Method m : getTestClass().getJavaClass().getMethods()) { | |||
Parameters p = m.getAnnotation(Parameters.class); | |||
if (p == null) { | |||
continue; | |||
} | |||
if (!m.getName().startsWith("get") || !m.getName().endsWith("s")) { | |||
throw new IllegalStateException( | |||
"Method " | |||
+ m.getName() | |||
+ " is annotated with @Parameter but is not named getSomeThings() as it should"); | |||
} | |||
if (m.getParameterTypes().length != 0) { | |||
throw new IllegalStateException( | |||
"Method " | |||
+ m.getName() | |||
+ " annotated with @Parameter should not have any arguments"); | |||
} | |||
if (!Modifier.isStatic(m.getModifiers())) { | |||
throw new IllegalStateException("Method " + m.getName() | |||
+ " annotated with @Parameter must be static"); | |||
} | |||
// getThemes -> setTheme | |||
String setter = "set" + m.getName().substring("get".length()); | |||
setter = setter.substring(0, setter.length() - 1); | |||
// property = property.substring(0, 1).toLowerCase() | |||
// + property.substring(1); | |||
Method setterMethod; | |||
try { | |||
setterMethod = getTestClass().getJavaClass().getMethod(setter, | |||
String.class); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("No setter " + setter | |||
+ " found in " | |||
+ getTestClass().getJavaClass().getName(), e); | |||
} | |||
Collection<String> values; | |||
try { | |||
values = (Collection<String>) m.invoke(null); | |||
if (!values.isEmpty()) { | |||
// Ignore any empty collections to allow e.g. integration | |||
// tests to use "/demo" path by default without adding that | |||
// to the screenshot name | |||
parameters.put(setterMethod, values); | |||
} | |||
} catch (Exception e) { | |||
throw new IllegalStateException("The setter " + m.getName() | |||
+ " could not be invoked", e); | |||
} | |||
} | |||
// Add method permutations for all @Parameters | |||
for (Method setter : parameters.keySet()) { | |||
List<FrameworkMethod> newMethods = new ArrayList<FrameworkMethod>(); | |||
for (FrameworkMethod m : methods) { | |||
if (!(m instanceof TBMethod)) { | |||
System.err.println("Unknown method type: " | |||
+ m.getClass().getName()); | |||
newMethods.add(m); | |||
continue; | |||
} | |||
// testFoo | |||
// testBar | |||
// -> | |||
// testFoo[valo] | |||
// testFoo[runo] | |||
// testBar[valo] | |||
// testBar[runo] | |||
for (final String value : parameters.get(setter)) { | |||
newMethods.add(new TBMethodWithBefore((TBMethod) m, setter, | |||
value)); | |||
} | |||
} | |||
// Update methods so next parameters will use all expanded methods | |||
methods = newMethods; | |||
} | |||
return methods; | |||
} | |||
public static class TBMethodWithBefore extends TBMethod { | |||
private Method setter; | |||
private String value; | |||
private TBMethod parent; | |||
public TBMethodWithBefore(TBMethod m, Method setter, String value) { | |||
super(m.getMethod(), m.getCapabilities()); | |||
parent = m; | |||
this.setter = setter; | |||
this.value = value; | |||
} | |||
@Override | |||
public Object invokeExplosively(Object target, Object... params) | |||
throws Throwable { | |||
setter.invoke(target, value); | |||
return parent.invokeExplosively(target, params); | |||
} | |||
@Override | |||
public String getName() { | |||
return parent.getName() + "[" + value + "]"; | |||
}; | |||
} | |||
} |
@@ -50,7 +50,7 @@ public class BasicPush extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
getReconnectDialogConfiguration().setDialogModal(false); | |||
spacer(); | |||
/* |
@@ -16,6 +16,7 @@ | |||
package com.vaadin.tests.push; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.WebDriver; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.support.ui.ExpectedCondition; | |||
@@ -52,8 +53,8 @@ public abstract class BasicPushTest extends MultiBrowserTest { | |||
} | |||
public static int getClientCounter(AbstractTB3Test t) { | |||
WebElement clientCounterElem = t | |||
.vaadinElementById(BasicPush.CLIENT_COUNTER_ID); | |||
WebElement clientCounterElem = t.findElement(By | |||
.id(BasicPush.CLIENT_COUNTER_ID)); | |||
return Integer.parseInt(clientCounterElem.getText()); | |||
} | |||
@@ -66,21 +67,21 @@ public abstract class BasicPushTest extends MultiBrowserTest { | |||
} | |||
public static int getServerCounter(AbstractTB3Test t) { | |||
WebElement serverCounterElem = t | |||
.vaadinElementById(BasicPush.SERVER_COUNTER_ID); | |||
WebElement serverCounterElem = t.findElement(By | |||
.id(BasicPush.SERVER_COUNTER_ID)); | |||
return Integer.parseInt(serverCounterElem.getText()); | |||
} | |||
public static WebElement getServerCounterStartButton(AbstractTB3Test t) { | |||
return t.vaadinElementById(BasicPush.START_TIMER_ID); | |||
return t.findElement(By.id(BasicPush.START_TIMER_ID)); | |||
} | |||
public static WebElement getServerCounterStopButton(AbstractTB3Test t) { | |||
return t.vaadinElementById(BasicPush.STOP_TIMER_ID); | |||
return t.findElement(By.id(BasicPush.STOP_TIMER_ID)); | |||
} | |||
public static WebElement getIncrementButton(AbstractTB3Test t) { | |||
return t.vaadinElementById(BasicPush.INCREMENT_BUTTON_ID); | |||
return t.findElement(By.id(BasicPush.INCREMENT_BUTTON_ID)); | |||
} | |||
protected void waitUntilClientCounterChanges(final int expectedValue) { |
@@ -0,0 +1,34 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.push; | |||
import com.vaadin.annotations.Push; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.ui.ui.Transport; | |||
import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; | |||
@Push(transport = Transport.WEBSOCKET_XHR) | |||
public class BasicPushWebsocketXhr extends BasicPush { | |||
@Override | |||
public void init(VaadinRequest request) { | |||
super.init(request); | |||
// Don't use fallback so we can easier detect if websocket fails | |||
getPushConfiguration().setParameter( | |||
PushConfigurationState.FALLBACK_TRANSPORT_PARAM, "none"); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.push; | |||
import java.util.List; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.tests.tb3.WebsocketTest; | |||
public class BasicPushWebsocketXhrTest extends BasicPushTest { | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
return getBrowsersSupportingWebSocket(); | |||
} | |||
} |
@@ -36,7 +36,7 @@ public abstract class SendMultibyteCharactersTest extends MultiBrowserTest { | |||
findElement(By.tagName("body")).click(); | |||
waitForDebugMessage("Variable burst to be sent to server:", 5); | |||
waitForDebugMessage("RPC invocations to be sent to the server:", 5); | |||
waitForDebugMessage("Handling message from server", 10); | |||
} | |||
@@ -15,6 +15,9 @@ | |||
*/ | |||
package com.vaadin.tests.requesthandlers; | |||
import java.io.IOException; | |||
import java.io.PrintWriter; | |||
import com.vaadin.launcher.ApplicationRunnerServlet; | |||
import com.vaadin.server.CustomizedSystemMessages; | |||
import com.vaadin.server.SystemMessages; | |||
@@ -69,7 +72,17 @@ public class CommunicationError extends UIProvider { | |||
@Override | |||
public void buttonClick(ClickEvent event) { | |||
VaadinService.getCurrentResponse().setStatus(400); | |||
try { | |||
// An unparseable response will cause | |||
// communication error | |||
PrintWriter writer = VaadinService | |||
.getCurrentResponse().getWriter(); | |||
writer.write("for(;;)[{FOOBAR}]"); | |||
writer.flush(); | |||
writer.close(); | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
} | |||
} | |||
}); | |||
addComponent(button); |
@@ -37,6 +37,7 @@ import org.apache.http.impl.client.DefaultHttpClient; | |||
import org.apache.http.message.BasicHttpEntityEnclosingRequest; | |||
import org.junit.Assert; | |||
import org.junit.Rule; | |||
import org.junit.rules.TestName; | |||
import org.junit.runner.RunWith; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Dimension; | |||
@@ -97,6 +98,9 @@ import elemental.json.impl.JsonUtil; | |||
@RunWith(TB3Runner.class) | |||
public abstract class AbstractTB3Test extends ParallelTest { | |||
@Rule | |||
public TestName testName = new TestName(); | |||
@Rule | |||
public RetryOnFail retry = new RetryOnFail(); | |||
@@ -465,6 +469,15 @@ public abstract class AbstractTB3Test extends ParallelTest { | |||
waitUntil(ExpectedConditions.presenceOfElementLocated(by)); | |||
} | |||
protected void waitForElementNotPresent(final By by) { | |||
waitUntil(new ExpectedCondition<Boolean>() { | |||
@Override | |||
public Boolean apply(WebDriver input) { | |||
return input.findElements(by).isEmpty(); | |||
} | |||
}); | |||
} | |||
protected void waitForElementVisible(final By by) { | |||
waitUntil(ExpectedConditions.visibilityOfElementLocated(by)); | |||
} |
@@ -118,7 +118,7 @@ public class CustomTestBenchCommandExecutor { | |||
* @return | |||
* @throws IOException | |||
*/ | |||
private BufferedImage cropToElement(WebElement element, | |||
public static BufferedImage cropToElement(WebElement element, | |||
BufferedImage fullScreen, boolean isIE8) throws IOException { | |||
Point loc = element.getLocation(); | |||
Dimension size = element.getSize(); |
@@ -0,0 +1,44 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.tb3; | |||
import java.lang.annotation.ElementType; | |||
import java.lang.annotation.Inherited; | |||
import java.lang.annotation.Retention; | |||
import java.lang.annotation.RetentionPolicy; | |||
import java.lang.annotation.Target; | |||
/** | |||
* Annotation to control inclusion of a test into a test suite. | |||
* <p> | |||
* The test will be included in the suite only if the given System property | |||
* {@code property} has the given {@code value}. | |||
* <p> | |||
* Used by {@link TB3TestLocator} | |||
* | |||
* @since | |||
* @author Vaadin Ltd | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target(ElementType.TYPE) | |||
@Inherited | |||
public @interface IncludeIfProperty { | |||
String property(); | |||
String value(); | |||
} |
@@ -20,8 +20,6 @@ import java.util.ArrayList; | |||
import java.util.Calendar; | |||
import java.util.List; | |||
import org.junit.Rule; | |||
import org.junit.rules.TestName; | |||
import org.openqa.selenium.ie.InternetExplorerDriver; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
@@ -46,9 +44,6 @@ import com.vaadin.testbench.parallel.BrowserUtil; | |||
*/ | |||
public abstract class MultiBrowserTest extends PrivateTB3Configuration { | |||
@Rule | |||
public TestName testName = new TestName(); | |||
protected List<DesiredCapabilities> getBrowsersSupportingWebSocket() { | |||
// No WebSocket support in IE8-9 and PhantomJS | |||
return getBrowserCapabilities(Browser.IE10, Browser.IE11, |
@@ -16,18 +16,34 @@ | |||
package com.vaadin.tests.tb3; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized.Parameters; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.tests.integration.ParameterizedTB3Runner; | |||
/** | |||
* Test which uses theme returned by {@link #getTheme()} for running the test | |||
*/ | |||
@RunWith(ParameterizedTB3Runner.class) | |||
public abstract class MultiBrowserThemeTest extends MultiBrowserTest { | |||
protected abstract String getTheme(); | |||
private String theme; | |||
public void setTheme(String theme) { | |||
this.theme = theme; | |||
} | |||
@Parameters | |||
public static Collection<String> getThemes() { | |||
return Arrays.asList(new String[] { "valo", "reindeer", "runo", | |||
"chameleon", "base" }); | |||
} | |||
@Override | |||
protected boolean requireWindowFocusForIE() { | |||
@@ -37,7 +53,7 @@ public abstract class MultiBrowserThemeTest extends MultiBrowserTest { | |||
@Override | |||
protected void openTestURL(Class<?> uiClass, String... parameters) { | |||
Set<String> params = new HashSet<String>(Arrays.asList(parameters)); | |||
params.add("theme=" + getTheme()); | |||
params.add("theme=" + theme); | |||
super.openTestURL(uiClass, params.toArray(new String[params.size()])); | |||
} | |||
@@ -0,0 +1,64 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.tb3; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.Parameterized.Parameters; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.tests.integration.ParameterizedTB3Runner; | |||
@RunWith(ParameterizedTB3Runner.class) | |||
public abstract class MultiBrowserThemeTestWithProxy extends | |||
MultiBrowserTestWithProxy { | |||
private String theme; | |||
public void setTheme(String theme) { | |||
this.theme = theme; | |||
} | |||
@Parameters | |||
public static Collection<String> getThemes() { | |||
return Arrays.asList(new String[] { "valo", "reindeer", "runo", | |||
"chameleon", "base" }); | |||
} | |||
@Override | |||
protected boolean requireWindowFocusForIE() { | |||
return true; | |||
} | |||
@Override | |||
protected void openTestURL(Class<?> uiClass, String... parameters) { | |||
Set<String> params = new HashSet<String>(Arrays.asList(parameters)); | |||
params.add("theme=" + theme); | |||
super.openTestURL(uiClass, params.toArray(new String[params.size()])); | |||
} | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
List<DesiredCapabilities> browsersToTest = getBrowsersExcludingPhantomJS(); | |||
browsersToTest.add(PHANTOMJS2()); | |||
return browsersToTest; | |||
} | |||
} |
@@ -381,8 +381,7 @@ public abstract class ScreenshotTB3Test extends AbstractTB3Test { | |||
* fails | |||
*/ | |||
private String getScreenshotFailureName() { | |||
return getScreenshotBaseName() + "_" | |||
+ getUniqueIdentifier(getDesiredCapabilities()) | |||
return getScreenshotBaseName() + "_" + getUniqueIdentifier(null) | |||
+ "-failure.png"; | |||
} | |||
@@ -418,52 +417,34 @@ public abstract class ScreenshotTB3Test extends AbstractTB3Test { | |||
*/ | |||
private String getScreenshotReferenceName(String identifier, | |||
Integer versionOverride) { | |||
String uniqueBrowserIdentifier; | |||
if (versionOverride == null) { | |||
uniqueBrowserIdentifier = getUniqueIdentifier(getDesiredCapabilities()); | |||
} else { | |||
uniqueBrowserIdentifier = getUniqueIdentifier( | |||
getDesiredCapabilities(), "" + versionOverride); | |||
} | |||
// WindowMaximizeRestoreTest_Windows_InternetExplorer_8_window-1-moved-maximized-restored.png | |||
return getScreenshotReferenceDirectory() + File.separator | |||
+ getScreenshotBaseName() + "_" + uniqueBrowserIdentifier + "_" | |||
+ identifier + ".png"; | |||
+ getScreenshotBaseName() + "_" | |||
+ getUniqueIdentifier(versionOverride) + "_" + identifier | |||
+ ".png"; | |||
} | |||
/** | |||
* Returns a string which uniquely (enough) identifies this browser. Used | |||
* mainly in screenshot names. | |||
* | |||
* @param capabilities | |||
* @param versionOverride | |||
* | |||
* @return a unique string for each browser | |||
*/ | |||
private String getUniqueIdentifier(DesiredCapabilities capabilities, | |||
String versionOverride) { | |||
return getUniqueIdentifier(BrowserUtil.getPlatform(capabilities), | |||
BrowserUtil.getBrowserIdentifier(capabilities), versionOverride); | |||
} | |||
private String getUniqueIdentifier(Integer versionOverride) { | |||
String testNameAndParameters = testName.getMethodName(); | |||
// runTest-wildfly9-nginx[Windows_Firefox_24][/buffering/demo][valo] | |||
/** | |||
* Returns a string which uniquely (enough) identifies this browser. Used | |||
* mainly in screenshot names. | |||
* | |||
* @param capabilities | |||
* | |||
* @return a unique string for each browser | |||
*/ | |||
private String getUniqueIdentifier(DesiredCapabilities capabilities) { | |||
return getUniqueIdentifier(BrowserUtil.getPlatform(capabilities), | |||
BrowserUtil.getBrowserIdentifier(capabilities), | |||
capabilities.getVersion()); | |||
} | |||
String parameters = testNameAndParameters.substring( | |||
testNameAndParameters.indexOf("[") + 1, | |||
testNameAndParameters.length() - 1); | |||
// Windows_Firefox_24][/buffering/demo][valo | |||
parameters = parameters.replace("][", "_"); | |||
// Windows_Firefox_24_/buffering/demo_valo | |||
private String getUniqueIdentifier(String platform, String browser, | |||
String version) { | |||
return platform + "_" + browser + "_" + version; | |||
parameters = parameters.replace("/", ""); | |||
// Windows_Firefox_24_bufferingdemo_valo | |||
if (versionOverride != null) { | |||
// Windows_Firefox_17_bufferingdemo_valo | |||
parameters = parameters.replaceFirst("_" | |||
+ getDesiredCapabilities().getVersion(), "_" | |||
+ versionOverride); | |||
} | |||
return parameters; | |||
} | |||
/** |
@@ -24,7 +24,7 @@ import java.util.Set; | |||
import org.junit.runner.RunWith; | |||
import org.junit.runners.model.InitializationError; | |||
import com.vaadin.tests.integration.AbstractServletIntegrationTest; | |||
import com.vaadin.tests.integration.AbstractIntegrationTest; | |||
import com.vaadin.tests.integration.ServletIntegrationJSR356WebsocketUITest; | |||
import com.vaadin.tests.integration.ServletIntegrationWebsocketUITest; | |||
import com.vaadin.tests.tb3.ServletIntegrationTests.ServletIntegrationTestSuite; | |||
@@ -51,6 +51,7 @@ public class ServletIntegrationTests { | |||
notWebsocketCompatible.add("tomcat6"); | |||
notWebsocketCompatible.add("tomcat7apacheproxy"); | |||
notWebsocketCompatible.add("weblogic10"); | |||
notWebsocketCompatible.add("wildfly9-nginx"); | |||
// Requires an update to 8.5.5 and a fix for | |||
// https://dev.vaadin.com/ticket/16354 | |||
@@ -65,7 +66,7 @@ public class ServletIntegrationTests { | |||
public static class ServletIntegrationTestSuite extends TB3TestSuite { | |||
public ServletIntegrationTestSuite(Class<?> klass) | |||
throws InitializationError, IOException { | |||
super(klass, AbstractServletIntegrationTest.class, | |||
super(klass, AbstractIntegrationTest.class, | |||
"com.vaadin.tests.integration", new String[] {}, | |||
new ServletTestLocator()); | |||
} |
@@ -22,7 +22,7 @@ import java.lang.reflect.Modifier; | |||
import org.apache.http.client.HttpClient; | |||
import org.junit.runners.Parameterized; | |||
import org.junit.runners.model.InitializationError; | |||
import org.openqa.selenium.remote.HttpCommandExecutor; | |||
import org.openqa.selenium.remote.internal.ApacheHttpClient; | |||
import org.openqa.selenium.remote.internal.HttpClientFactory; | |||
import com.vaadin.testbench.parallel.ParallelRunner; | |||
@@ -48,8 +48,8 @@ public class TB3Runner extends ParallelRunner { | |||
// reduce socket timeout to avoid tests hanging for three hours | |||
try { | |||
Field field = HttpCommandExecutor.class | |||
.getDeclaredField("httpClientFactory"); | |||
Field field = ApacheHttpClient.Factory.class | |||
.getDeclaredField("defaultClientFactory"); | |||
assert (Modifier.isStatic(field.getModifiers())); | |||
field.setAccessible(true); | |||
field.set(null, new HttpClientFactory() { |
@@ -213,6 +213,17 @@ public class TB3TestLocator { | |||
return false; | |||
} | |||
IncludeIfProperty includeIfProperty = c | |||
.getAnnotation(IncludeIfProperty.class); | |||
if (includeIfProperty != null) { | |||
String includeValue = includeIfProperty.value(); | |||
String systemPropertyValue = System.getProperty(includeIfProperty | |||
.property()); | |||
if (!includeValue.equals(systemPropertyValue)) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
} |
@@ -71,7 +71,7 @@ public class ThemeChangeOnTheFlyTest extends MultiBrowserTest { | |||
public void reindeerToNullToReindeer() throws IOException { | |||
openTestURL(); | |||
changeThemeAndCompare("null"); | |||
changeTheme("null"); | |||
changeThemeAndCompare("reindeer"); | |||
} | |||
@@ -15,17 +15,9 @@ | |||
*/ | |||
package com.vaadin.tests.widgetset.client; | |||
import java.util.Date; | |||
import java.util.logging.Logger; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ValueMap; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import com.vaadin.tests.widgetset.server.csrf.ui.CsrfTokenDisabled; | |||
import elemental.json.JsonObject; | |||
import elemental.json.JsonValue; | |||
/** | |||
* Mock ApplicationConnection for several issues where we need to hack it. | |||
* | |||
@@ -34,14 +26,24 @@ import elemental.json.JsonValue; | |||
*/ | |||
public class MockApplicationConnection extends ApplicationConnection { | |||
private static final Logger LOGGER = Logger | |||
.getLogger(MockApplicationConnection.class.getName()); | |||
public MockApplicationConnection() { | |||
super(); | |||
messageHandler = new MockServerMessageHandler(); | |||
messageHandler.setConnection(this); | |||
messageSender = new MockServerCommunicationHandler(); | |||
messageSender.setConnection(this); | |||
} | |||
// The last token received from the server. | |||
private String lastCsrfTokenReceiver; | |||
@Override | |||
public MockServerMessageHandler getMessageHandler() { | |||
return (MockServerMessageHandler) super.getMessageHandler(); | |||
} | |||
// The last token sent to the server. | |||
private String lastCsrfTokenSent; | |||
@Override | |||
public MockServerCommunicationHandler getMessageSender() { | |||
return (MockServerCommunicationHandler) super | |||
.getMessageSender(); | |||
} | |||
/** | |||
* Provide the last token received from the server. <br/> | |||
@@ -50,7 +52,7 @@ public class MockApplicationConnection extends ApplicationConnection { | |||
* @see CsrfTokenDisabled | |||
*/ | |||
public String getLastCsrfTokenReceiver() { | |||
return lastCsrfTokenReceiver; | |||
return getMessageHandler().lastCsrfTokenReceiver; | |||
} | |||
/** | |||
@@ -60,23 +62,7 @@ public class MockApplicationConnection extends ApplicationConnection { | |||
* @see CsrfTokenDisabled | |||
*/ | |||
public String getLastCsrfTokenSent() { | |||
return lastCsrfTokenSent; | |||
} | |||
@Override | |||
protected void handleUIDLMessage(Date start, String jsonText, ValueMap json) { | |||
lastCsrfTokenReceiver = json | |||
.getString(ApplicationConstants.UIDL_SECURITY_TOKEN_ID); | |||
super.handleUIDLMessage(start, jsonText, json); | |||
} | |||
@Override | |||
protected void doUidlRequest(String uri, JsonObject payload) { | |||
JsonValue jsonValue = payload.get(ApplicationConstants.CSRF_TOKEN); | |||
lastCsrfTokenSent = jsonValue != null ? jsonValue.toJson() : null; | |||
super.doUidlRequest(uri, payload); | |||
return getMessageSender().lastCsrfTokenSent; | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.widgetset.client; | |||
import com.vaadin.client.communication.MessageSender; | |||
import com.vaadin.shared.ApplicationConstants; | |||
import elemental.json.JsonObject; | |||
import elemental.json.JsonValue; | |||
public class MockServerCommunicationHandler extends MessageSender { | |||
// The last token sent to the server. | |||
String lastCsrfTokenSent; | |||
@Override | |||
public void send(JsonObject payload) { | |||
JsonValue jsonValue = payload.get(ApplicationConstants.CSRF_TOKEN); | |||
lastCsrfTokenSent = jsonValue != null ? jsonValue.toJson() : null; | |||
super.send(payload); | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
/* | |||
* Copyright 2000-2014 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 | |||
* the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |||
* License for the specific language governing permissions and limitations under | |||
* the License. | |||
*/ | |||
package com.vaadin.tests.widgetset.client; | |||
import com.vaadin.client.ValueMap; | |||
import com.vaadin.client.communication.MessageHandler; | |||
import com.vaadin.shared.ApplicationConstants; | |||
public class MockServerMessageHandler extends MessageHandler { | |||
// The last token received from the server. | |||
protected String lastCsrfTokenReceiver; | |||
@Override | |||
public void handleJSON(ValueMap json) { | |||
lastCsrfTokenReceiver = json | |||
.getString(ApplicationConstants.UIDL_SECURITY_TOKEN_ID); | |||
super.handleJSON(json); | |||
} | |||
} |
@@ -2,7 +2,6 @@ package com.vaadin.tests.widgetset.client; | |||
import com.google.gwt.user.client.Window; | |||
import com.vaadin.client.ApplicationConnection; | |||
import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; | |||
import com.vaadin.client.communication.AtmospherePushConnection; | |||
import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; | |||
@@ -12,9 +11,8 @@ public class TestingPushConnection extends AtmospherePushConnection { | |||
@Override | |||
public void init(ApplicationConnection connection, | |||
PushConfigurationState pushConfiguration, | |||
CommunicationErrorHandler errorHandler) { | |||
super.init(connection, pushConfiguration, errorHandler); | |||
PushConfigurationState pushConfiguration) { | |||
super.init(connection, pushConfiguration); | |||
transport = Window.Location.getParameter("transport"); | |||
} | |||
@@ -70,8 +70,8 @@ public class CsrfButtonConnector extends AbstractComponentConnector { | |||
} | |||
private String csrfTokenInfo() { | |||
return getMockConnection().getCsrfToken() + ", " | |||
+ getMockConnection().getLastCsrfTokenReceiver() + ", " | |||
return getMockConnection().getMessageHandler().getCsrfToken() | |||
+ ", " + getMockConnection().getLastCsrfTokenReceiver() + ", " | |||
+ getMockConnection().getLastCsrfTokenSent(); | |||
} | |||