Pārlūkot izejas kodu

Merge remote-tracking branch 'origin/reconnect-dialog' (#11733)

Change-Id: Id148ac8a5b86a76ed966f96ea7732c35ad0d056d
tags/7.6.0.alpha5
Artur Signell pirms 8 gadiem
vecāks
revīzija
e54434a932
92 mainītis faili ar 6288 papildinājumiem un 2707 dzēšanām
  1. 2
    0
      WebContent/VAADIN/themes/base/base.scss
  2. Binārs
      WebContent/VAADIN/themes/base/common/img/reconnect-spinner.gif
  3. 32
    0
      WebContent/VAADIN/themes/base/common/reconnect-dialog.scss
  4. 2
    0
      WebContent/VAADIN/themes/valo/shared/_global.scss
  5. 31
    0
      WebContent/VAADIN/themes/valo/shared/_reconnect-dialog.scss
  6. 18
    0
      WebContent/WEB-INF/web.xml
  7. 4
    0
      client/src/com/vaadin/DefaultWidgetSet.gwt.xml
  8. 1
    1
      client/src/com/vaadin/client/ApplicationConfiguration.java
  9. 242
    2343
      client/src/com/vaadin/client/ApplicationConnection.java
  10. 10
    7
      client/src/com/vaadin/client/JavaScriptConnectorHelper.java
  11. 9
    2
      client/src/com/vaadin/client/LayoutManager.java
  12. 1
    1
      client/src/com/vaadin/client/LayoutManagerIE8.java
  13. 36
    25
      client/src/com/vaadin/client/Util.java
  14. 2
    2
      client/src/com/vaadin/client/ValueMap.java
  15. 17
    0
      client/src/com/vaadin/client/WidgetUtil.java
  16. 71
    55
      client/src/com/vaadin/client/communication/AtmospherePushConnection.java
  17. 202
    0
      client/src/com/vaadin/client/communication/ConnectionStateHandler.java
  18. 597
    0
      client/src/com/vaadin/client/communication/DefaultConnectionStateHandler.java
  19. 117
    0
      client/src/com/vaadin/client/communication/DefaultReconnectDialog.java
  20. 9
    27
      client/src/com/vaadin/client/communication/Heartbeat.java
  21. 1747
    0
      client/src/com/vaadin/client/communication/MessageHandler.java
  22. 410
    0
      client/src/com/vaadin/client/communication/MessageSender.java
  23. 24
    8
      client/src/com/vaadin/client/communication/PushConnection.java
  24. 93
    0
      client/src/com/vaadin/client/communication/ReconnectDialog.java
  25. 6
    2
      client/src/com/vaadin/client/communication/RpcProxy.java
  26. 342
    0
      client/src/com/vaadin/client/communication/ServerRpcQueue.java
  27. 4
    1
      client/src/com/vaadin/client/communication/TranslatedURLReference.java
  28. 276
    0
      client/src/com/vaadin/client/communication/XhrConnection.java
  29. 106
    0
      client/src/com/vaadin/client/communication/XhrConnectionError.java
  30. 1
    1
      client/src/com/vaadin/client/debug/internal/InfoSection.java
  31. 6
    4
      client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java
  32. 2
    2
      client/src/com/vaadin/client/ui/VNotification.java
  33. 2
    1
      client/src/com/vaadin/client/ui/VScrollTable.java
  34. 6
    1
      client/src/com/vaadin/client/ui/VUpload.java
  35. 1
    2
      client/src/com/vaadin/client/ui/datefield/DateFieldConnector.java
  36. 2
    1
      client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java
  37. 2
    2
      client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
  38. 8
    5
      client/src/com/vaadin/client/ui/ui/UIConnector.java
  39. 54
    0
      client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java
  40. 2
    0
      ivysettings.xml
  41. 4
    0
      server/src/com/vaadin/server/LegacyCommunicationManager.java
  42. 18
    2
      server/src/com/vaadin/server/LocaleService.java
  43. 1
    1
      server/src/com/vaadin/server/communication/AtmospherePushConnection.java
  44. 96
    2
      server/src/com/vaadin/server/communication/ServerRpcHandler.java
  45. 1
    1
      server/src/com/vaadin/server/communication/UIInitHandler.java
  46. 3
    45
      server/src/com/vaadin/server/communication/UidlRequestHandler.java
  47. 11
    6
      server/src/com/vaadin/server/communication/UidlWriter.java
  48. 21
    3
      server/src/com/vaadin/ui/PushConfiguration.java
  49. 201
    0
      server/src/com/vaadin/ui/ReconnectDialogConfiguration.java
  50. 45
    0
      server/src/com/vaadin/ui/UI.java
  51. 5
    0
      server/src/com/vaadin/ui/Upload.java
  52. 6
    0
      server/tests/src/com/vaadin/ui/PushConfigurationTransportTest.java
  53. 12
    0
      shared/src/com/vaadin/shared/ApplicationConstants.java
  54. 6
    0
      shared/src/com/vaadin/shared/ui/ui/Transport.java
  55. 12
    0
      shared/src/com/vaadin/shared/ui/ui/UIState.java
  56. 8
    0
      shared/src/com/vaadin/shared/ui/upload/UploadServerRpc.java
  57. 7
    0
      uitest/integration_tests.xml
  58. 7
    4
      uitest/ivy.xml
  59. 151
    0
      uitest/src/com/vaadin/tests/application/CommErrorEmulatorServlet.java
  60. 270
    0
      uitest/src/com/vaadin/tests/application/CommErrorEmulatorUI.java
  61. 1
    42
      uitest/src/com/vaadin/tests/application/CriticalNotificationsTest.java
  62. 101
    0
      uitest/src/com/vaadin/tests/application/ReconnectDialogThemeTest.java
  63. 46
    0
      uitest/src/com/vaadin/tests/application/ReconnectDialogUI.java
  64. 82
    0
      uitest/src/com/vaadin/tests/application/ReconnectDialogUITest.java
  65. 2
    0
      uitest/src/com/vaadin/tests/components/AbstractTestUI.java
  66. 2
    1
      uitest/src/com/vaadin/tests/components/grid/GridThemeChangeTest.java
  67. 2
    1
      uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java
  68. 31
    1
      uitest/src/com/vaadin/tests/integration/AbstractServletIntegrationTest.java
  69. 107
    0
      uitest/src/com/vaadin/tests/integration/LongPollingProxyServerTest.java
  70. 170
    0
      uitest/src/com/vaadin/tests/integration/ParameterizedTB3Runner.java
  71. 1
    1
      uitest/src/com/vaadin/tests/push/BasicPush.java
  72. 8
    7
      uitest/src/com/vaadin/tests/push/BasicPushTest.java
  73. 34
    0
      uitest/src/com/vaadin/tests/push/BasicPushWebsocketXhr.java
  74. 29
    0
      uitest/src/com/vaadin/tests/push/BasicPushWebsocketXhrTest.java
  75. 1
    1
      uitest/src/com/vaadin/tests/push/SendMultibyteCharactersTest.java
  76. 14
    1
      uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java
  77. 13
    0
      uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java
  78. 1
    1
      uitest/src/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java
  79. 44
    0
      uitest/src/com/vaadin/tests/tb3/IncludeIfProperty.java
  80. 0
    5
      uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java
  81. 18
    2
      uitest/src/com/vaadin/tests/tb3/MultiBrowserThemeTest.java
  82. 64
    0
      uitest/src/com/vaadin/tests/tb3/MultiBrowserThemeTestWithProxy.java
  83. 24
    43
      uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java
  84. 3
    2
      uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java
  85. 3
    3
      uitest/src/com/vaadin/tests/tb3/TB3Runner.java
  86. 11
    0
      uitest/src/com/vaadin/tests/tb3/TB3TestLocator.java
  87. 1
    1
      uitest/src/com/vaadin/tests/themes/ThemeChangeOnTheFlyTest.java
  88. 18
    32
      uitest/src/com/vaadin/tests/widgetset/client/MockApplicationConnection.java
  89. 36
    0
      uitest/src/com/vaadin/tests/widgetset/client/MockServerCommunicationHandler.java
  90. 35
    0
      uitest/src/com/vaadin/tests/widgetset/client/MockServerMessageHandler.java
  91. 2
    4
      uitest/src/com/vaadin/tests/widgetset/client/TestingPushConnection.java
  92. 2
    2
      uitest/src/com/vaadin/tests/widgetset/client/csrf/CsrfButtonConnector.java

+ 2
- 0
WebContent/VAADIN/themes/base/base.scss Parādīt failu

@@ -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;

Binārs
WebContent/VAADIN/themes/base/common/img/reconnect-spinner.gif Parādīt failu


+ 32
- 0
WebContent/VAADIN/themes/base/common/reconnect-dialog.scss Parādīt failu

@@ -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
- 0
WebContent/VAADIN/themes/valo/shared/_global.scss Parādīt failu

@@ -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;
}



+ 31
- 0
WebContent/VAADIN/themes/valo/shared/_reconnect-dialog.scss Parādīt failu

@@ -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;
}
}
}

+ 18
- 0
WebContent/WEB-INF/web.xml Parādīt failu

@@ -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>

+ 4
- 0
client/src/com/vaadin/DefaultWidgetSet.gwt.xml Parādīt failu

@@ -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

+ 1
- 1
client/src/com/vaadin/client/ApplicationConfiguration.java Parādīt failu

@@ -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 {

+ 242
- 2343
client/src/com/vaadin/client/ApplicationConnection.java
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 10
- 7
client/src/com/vaadin/client/JavaScriptConnectorHelper.java Parādīt failu

@@ -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) {

+ 9
- 2
client/src/com/vaadin/client/LayoutManager.java Parādīt failu

@@ -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() {

+ 1
- 1
client/src/com/vaadin/client/LayoutManagerIE8.java Parādīt failu

@@ -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.

+ 36
- 25
client/src/com/vaadin/client/Util.java Parādīt failu

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


+ 2
- 2
client/src/com/vaadin/client/ValueMap.java Parādīt failu

@@ -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];
}-*/;

+ 17
- 0
client/src/com/vaadin/client/WidgetUtil.java Parādīt failu

@@ -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

+ 71
- 55
client/src/com/vaadin/client/communication/AtmospherePushConnection.java Parādīt failu

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

}

+ 202
- 0
client/src/com/vaadin/client/communication/ConnectionStateHandler.java Parādīt failu

@@ -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();

}

+ 597
- 0
client/src/com/vaadin/client/communication/DefaultConnectionStateHandler.java Parādīt failu

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

}

+ 117
- 0
client/src/com/vaadin/client/communication/DefaultReconnectDialog.java Parādīt failu

@@ -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();

}
});
}
}

+ 9
- 27
client/src/com/vaadin/client/communication/Heartbeat.java Parādīt failu

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

+ 1747
- 0
client/src/com/vaadin/client/communication/MessageHandler.java
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 410
- 0
client/src/com/vaadin/client/communication/MessageSender.java Parādīt failu

@@ -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
}
}

}

+ 24
- 8
client/src/com/vaadin/client/communication/PushConnection.java Parādīt failu

@@ -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();

}

+ 93
- 0
client/src/com/vaadin/client/communication/ReconnectDialog.java Parādīt failu

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

+ 6
- 2
client/src/com/vaadin/client/communication/RpcProxy.java Parādīt failu

@@ -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;
}

+ 342
- 0
client/src/com/vaadin/client/communication/ServerRpcQueue.java Parādīt failu

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

}

+ 4
- 1
client/src/com/vaadin/client/communication/TranslatedURLReference.java Parādīt failu

@@ -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;

+ 276
- 0
client/src/com/vaadin/client/communication/XhrConnection.java Parādīt failu

@@ -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;
}
}-*/;

}

+ 106
- 0
client/src/com/vaadin/client/communication/XhrConnectionError.java Parādīt failu

@@ -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;
}
}

+ 1
- 1
client/src/com/vaadin/client/debug/internal/InfoSection.java Parādīt failu

@@ -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

+ 6
- 4
client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java Parādīt failu

@@ -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

+ 2
- 2
client/src/com/vaadin/client/ui/VNotification.java Parādīt failu

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

}

+ 2
- 1
client/src/com/vaadin/client/ui/VScrollTable.java Parādīt failu

@@ -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);

+ 6
- 1
client/src/com/vaadin/client/ui/VUpload.java Parādīt failu

@@ -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();

+ 1
- 2
client/src/com/vaadin/client/ui/datefield/DateFieldConnector.java Parādīt failu

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

+ 2
- 1
client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java Parādīt failu

@@ -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;
}

+ 2
- 2
client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java Parādīt failu

@@ -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;
}

+ 8
- 5
client/src/com/vaadin/client/ui/ui/UIConnector.java Parādīt failu

@@ -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);

+ 54
- 0
client/tests/src/com/vaadin/client/communication/ServerMessageHandlerTest.java Parādīt failu

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

}
}

+ 2
- 0
ivysettings.xml Parādīt failu

@@ -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"

+ 4
- 0
server/src/com/vaadin/server/LegacyCommunicationManager.java Parādīt failu

@@ -353,6 +353,10 @@ public class LegacyCommunicationManager implements Serializable {
res.clear();
}

public boolean isEmpty() {
return res.isEmpty();
}

}

/**

+ 18
- 2
server/src/com/vaadin/server/LocaleService.java Parādīt failu

@@ -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];

+ 1
- 1
server/src/com/vaadin/server/communication/AtmospherePushConnection.java Parādīt failu

@@ -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);

+ 96
- 2
server/src/com/vaadin/server/communication/ServerRpcHandler.java Parādīt failu

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

/**

+ 1
- 1
server/src/com/vaadin/server/communication/UIInitHandler.java Parādīt failu

@@ -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();

+ 3
- 45
server/src/com/vaadin/server/communication/UidlRequestHandler.java Parādīt failu

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

+ 11
- 6
server/src/com/vaadin/server/communication/UidlWriter.java Parādīt failu

@@ -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>>();


+ 21
- 3
server/src/com/vaadin/ui/PushConfiguration.java Parādīt failu

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

+ 201
- 0
server/src/com/vaadin/ui/ReconnectDialogConfiguration.java Parādīt failu

@@ -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;
}

}

+ 45
- 0
server/src/com/vaadin/ui/UI.java Parādīt failu

@@ -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;
}
}

+ 5
- 0
server/src/com/vaadin/ui/Upload.java Parādīt failu

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


+ 6
- 0
server/tests/src/com/vaadin/ui/PushConfigurationTransportTest.java Parādīt failu

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

}

+ 12
- 0
shared/src/com/vaadin/shared/ApplicationConstants.java Parādīt failu

@@ -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";

}

+ 6
- 0
shared/src/com/vaadin/shared/ui/ui/Transport.java Parādīt failu

@@ -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
*

+ 12
- 0
shared/src/com/vaadin/shared/ui/ui/UIState.java Parādīt failu

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

+ 8
- 0
shared/src/com/vaadin/shared/ui/upload/UploadServerRpc.java Parādīt failu

@@ -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();

}

+ 7
- 0
uitest/integration_tests.xml Parādīt failu

@@ -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" />

+ 7
- 4
uitest/ivy.xml Parādīt failu

@@ -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>

+ 151
- 0
uitest/src/com/vaadin/tests/application/CommErrorEmulatorServlet.java Parādīt failu

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

}

+ 270
- 0
uitest/src/com/vaadin/tests/application/CommErrorEmulatorUI.java Parādīt failu

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

}

uitest/src/com/vaadin/tests/application/CriticalNotificationsTestBase.java → uitest/src/com/vaadin/tests/application/CriticalNotificationsTest.java Parādīt failu

@@ -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 {

+ 101
- 0
uitest/src/com/vaadin/tests/application/ReconnectDialogThemeTest.java Parādīt failu

@@ -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;
}

}

+ 46
- 0
uitest/src/com/vaadin/tests/application/ReconnectDialogUI.java Parādīt failu

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

}

+ 82
- 0
uitest/src/com/vaadin/tests/application/ReconnectDialogUITest.java Parādīt failu

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

}

+ 2
- 0
uitest/src/com/vaadin/tests/components/AbstractTestUI.java Parādīt failu

@@ -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)) {

+ 2
- 1
uitest/src/com/vaadin/tests/components/grid/GridThemeChangeTest.java Parādīt failu

@@ -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(

+ 2
- 1
uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSortingTest.java Parādīt failu

@@ -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

+ 31
- 1
uitest/src/com/vaadin/tests/integration/AbstractServletIntegrationTest.java Parādīt failu

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

}

+ 107
- 0
uitest/src/com/vaadin/tests/integration/LongPollingProxyServerTest.java Parādīt failu

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

+ 170
- 0
uitest/src/com/vaadin/tests/integration/ParameterizedTB3Runner.java Parādīt failu

@@ -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 + "]";
};

}
}

+ 1
- 1
uitest/src/com/vaadin/tests/push/BasicPush.java Parādīt failu

@@ -50,7 +50,7 @@ public class BasicPush extends AbstractTestUI {

@Override
protected void setup(VaadinRequest request) {
getReconnectDialogConfiguration().setDialogModal(false);
spacer();

/*

+ 8
- 7
uitest/src/com/vaadin/tests/push/BasicPushTest.java Parādīt failu

@@ -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) {

+ 34
- 0
uitest/src/com/vaadin/tests/push/BasicPushWebsocketXhr.java Parādīt failu

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

}

+ 29
- 0
uitest/src/com/vaadin/tests/push/BasicPushWebsocketXhrTest.java Parādīt failu

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

+ 1
- 1
uitest/src/com/vaadin/tests/push/SendMultibyteCharactersTest.java Parādīt failu

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


+ 14
- 1
uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java Parādīt failu

@@ -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);

+ 13
- 0
uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java Parādīt failu

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

+ 1
- 1
uitest/src/com/vaadin/tests/tb3/CustomTestBenchCommandExecutor.java Parādīt failu

@@ -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();

+ 44
- 0
uitest/src/com/vaadin/tests/tb3/IncludeIfProperty.java Parādīt failu

@@ -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();

}

+ 0
- 5
uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java Parādīt failu

@@ -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,

+ 18
- 2
uitest/src/com/vaadin/tests/tb3/MultiBrowserThemeTest.java Parādīt failu

@@ -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()]));
}


+ 64
- 0
uitest/src/com/vaadin/tests/tb3/MultiBrowserThemeTestWithProxy.java Parādīt failu

@@ -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;
}
}

+ 24
- 43
uitest/src/com/vaadin/tests/tb3/ScreenshotTB3Test.java Parādīt failu

@@ -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;
}

/**

+ 3
- 2
uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java Parādīt failu

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

+ 3
- 3
uitest/src/com/vaadin/tests/tb3/TB3Runner.java Parādīt failu

@@ -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() {

+ 11
- 0
uitest/src/com/vaadin/tests/tb3/TB3TestLocator.java Parādīt failu

@@ -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;
}
}

+ 1
- 1
uitest/src/com/vaadin/tests/themes/ThemeChangeOnTheFlyTest.java Parādīt failu

@@ -71,7 +71,7 @@ public class ThemeChangeOnTheFlyTest extends MultiBrowserTest {
public void reindeerToNullToReindeer() throws IOException {
openTestURL();

changeThemeAndCompare("null");
changeTheme("null");
changeThemeAndCompare("reindeer");
}


+ 18
- 32
uitest/src/com/vaadin/tests/widgetset/client/MockApplicationConnection.java Parādīt failu

@@ -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;
}

}

+ 36
- 0
uitest/src/com/vaadin/tests/widgetset/client/MockServerCommunicationHandler.java Parādīt failu

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

+ 35
- 0
uitest/src/com/vaadin/tests/widgetset/client/MockServerMessageHandler.java Parādīt failu

@@ -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
- 4
uitest/src/com/vaadin/tests/widgetset/client/TestingPushConnection.java Parādīt failu

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


+ 2
- 2
uitest/src/com/vaadin/tests/widgetset/client/csrf/CsrfButtonConnector.java Parādīt failu

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


Notiek ielāde…
Atcelt
Saglabāt