diff options
8 files changed, 282 insertions, 3 deletions
diff --git a/WebContent/VAADIN/themes/valo/shared/_global.scss b/WebContent/VAADIN/themes/valo/shared/_global.scss index b4e8564119..39b5a4e7d9 100644 --- a/WebContent/VAADIN/themes/valo/shared/_global.scss +++ b/WebContent/VAADIN/themes/valo/shared/_global.scss @@ -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; } diff --git a/WebContent/VAADIN/themes/valo/shared/_reconnect-dialog.scss b/WebContent/VAADIN/themes/valo/shared/_reconnect-dialog.scss new file mode 100644 index 0000000000..94f5f8b0bd --- /dev/null +++ b/WebContent/VAADIN/themes/valo/shared/_reconnect-dialog.scss @@ -0,0 +1,18 @@ +@mixin valo-reconnect-dialog { + .v-reconnect-dialog { + color: white; + @include valo-notification-bar-style; + @include valo-notification-system-style; + text-align: center; + .spinner { + @include valo-spinner; + display: inline-block; + margin-top: 10px; + visibility: hidden; + } + + &.active .spinner { + visibility: visible; + } + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index b3bcbbb3ab..65afa9b08b 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -46,8 +46,8 @@ import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.CommunicationProblemHandler; -import com.vaadin.client.communication.DefaultCommunicationProblemHandler; import com.vaadin.client.communication.Heartbeat; +import com.vaadin.client.communication.ReconnectingCommunicationProblemHandler; import com.vaadin.client.communication.RpcManager; import com.vaadin.client.communication.ServerCommunicationHandler; import com.vaadin.client.communication.ServerMessageHandler; @@ -366,7 +366,7 @@ public class ApplicationConnection implements HasHandlers { serverRpcQueue = GWT.create(ServerRpcQueue.class); serverRpcQueue.setConnection(this); communicationProblemHandler = GWT - .create(DefaultCommunicationProblemHandler.class); + .create(ReconnectingCommunicationProblemHandler.class); communicationProblemHandler.setConnection(this); serverMessageHandler = GWT.create(ServerMessageHandler.class); serverMessageHandler.setConnection(this); diff --git a/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java index 8b97b0a4c7..196e1d56ea 100644 --- a/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java +++ b/client/src/com/vaadin/client/communication/DefaultCommunicationProblemHandler.java @@ -44,6 +44,10 @@ public class DefaultCommunicationProblemHandler implements this.connection = connection; } + protected ApplicationConnection getConnection() { + return connection; + } + public static Logger getLogger() { return Logger.getLogger(DefaultCommunicationProblemHandler.class .getName()); diff --git a/client/src/com/vaadin/client/communication/ReconnectDialog.java b/client/src/com/vaadin/client/communication/ReconnectDialog.java new file mode 100644 index 0000000000..b69f51c165 --- /dev/null +++ b/client/src/com/vaadin/client/communication/ReconnectDialog.java @@ -0,0 +1,49 @@ +/* + * 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.shared.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.Label; +import com.vaadin.client.ui.VOverlay; + +/** + * + * + * @since 7.6 + * @author Vaadin Ltd + */ +public class ReconnectDialog extends VOverlay { + interface MyUiBinder extends UiBinder<HTMLPanel, ReconnectDialog> { + } + + private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); + + @UiField + public Label label; + + public ReconnectDialog() { + super(false, true); + addStyleName("v-reconnect-dialog"); + setWidget(uiBinder.createAndBindUi(this)); + } + + public void setText(String text) { + label.setText(text); + } +} diff --git a/client/src/com/vaadin/client/communication/ReconnectDialog.ui.xml b/client/src/com/vaadin/client/communication/ReconnectDialog.ui.xml new file mode 100644 index 0000000000..885588f8a5 --- /dev/null +++ b/client/src/com/vaadin/client/communication/ReconnectDialog.ui.xml @@ -0,0 +1,12 @@ +<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' + xmlns:g='urn:import:com.google.gwt.user.client.ui'> + + <g:HTMLPanel> + + <g:Label ui:field="label" styleName="text" + text="Server connection lost, trying to reconnect..." /> + <div class="spinner" /> + </g:HTMLPanel> + +</ui:UiBinder> + diff --git a/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java b/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java new file mode 100644 index 0000000000..bcdf12f2a3 --- /dev/null +++ b/client/src/com/vaadin/client/communication/ReconnectingCommunicationProblemHandler.java @@ -0,0 +1,181 @@ +/* + * 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.shared.GWT; +import com.google.gwt.http.client.Request; +import com.google.gwt.http.client.Response; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; + +import elemental.json.JsonObject; + +//FIXME This is just a test and should be merged with DCPH +public class ReconnectingCommunicationProblemHandler extends + DefaultCommunicationProblemHandler { + + private enum Type { + HEARTBEAT, XHR; + } + + ReconnectDialog reconnectDialog = GWT.create(ReconnectDialog.class); + int reconnectAttempt = 0; + private Type reconnectionCause = null;; + + @Override + public void xhrException(CommunicationProblemEvent event) { + handleTemporaryError(Type.XHR, event.getPayload()); + } + + @Override + public boolean heartbeatException(Request request, Throwable exception) { + handleTemporaryError(Type.HEARTBEAT, null); + return true; + } + + @Override + public boolean heartbeatInvalidStatusCode(Request request, Response response) { + if (response.getStatusCode() == Response.SC_GONE) { + // Session expired + resolveTemporaryError(Type.HEARTBEAT, false); + return super.heartbeatInvalidStatusCode(request, response); + } + + handleTemporaryError(Type.HEARTBEAT, null); + return true; + } + + @Override + public void heartbeatOk() { + resolveTemporaryError(Type.HEARTBEAT, true); + } + + private void handleTemporaryError(Type type, final JsonObject payload) { + reconnectAttempt++; + reconnectionCause = type; + if (!reconnectDialog.isAttached()) { + // FIXME + reconnectDialog.setStyleName("active", true); + reconnectDialog.setOwner(getConnection().getUIConnector() + .getWidget()); + reconnectDialog.setPopupPositionAndShow(new PositionCallback() { + @Override + public void setPosition(int offsetWidth, int offsetHeight) { + // FIXME + reconnectDialog.setPopupPosition(0, 0); + } + }); + } + if (payload != null) { + getConnection().getServerCommunicationHandler().endRequest(); + } + + if (reconnectAttempt >= getMaxReconnectAttempts()) { + // FIXME Remove + reconnectDialog.setText("Server connection lost. Gave up after " + + reconnectAttempt + " attempts."); + // FIXME + reconnectDialog.setStyleName("active", false); + + getConnection().setApplicationRunning(false); + + } else { + reconnectDialog + .setText("Server connection lost, trying to reconnect... Attempt " + + reconnectAttempt); + + // Here and not in timer to avoid TB for getting in between + if (payload != null) { + // FIXME: Not like this + getConnection().getServerCommunicationHandler().startRequest(); + } + + // Reconnect + new Timer() { + @Override + public void run() { + if (payload != null) { + getLogger().info( + "Re-sending last message to the server..."); + getConnection().getServerCommunicationHandler().send( + payload); + } else { + // Use heartbeat + getLogger().info( + "Trying to re-establish server connection..."); + getConnection().getHeartbeat().send(); + } + } + }.schedule(getReconnectInterval()); + } + } + + /** + * @since + * @return + */ + private int getMaxReconnectAttempts() { + // FIXME Parameter + return 15; + } + + /** + * @since + * @return + */ + private int getReconnectInterval() { + // FIXME Parameter + return 5000; + } + + @Override + public void xhrInvalidContent(CommunicationProblemEvent event) { + super.xhrInvalidContent(event); + }; + + @Override + public void xhrInvalidStatusCode(CommunicationProblemEvent event) { + handleTemporaryError(Type.XHR, event.getPayload()); + } + + @Override + public void xhrOk() { + resolveTemporaryError(Type.XHR, true); + } + + private void resolveTemporaryError(Type cause, boolean success) { + if (reconnectionCause == null) { + // Not trying to reconnect + return; + } + if (reconnectionCause != cause) { + // If a heartbeat goes through while we are trying to re-send an + // XHR, we wait for the XHR to go through + return; + } + + reconnectionCause = null; + if (reconnectDialog.isAttached()) { + reconnectDialog.hide(); + } + + if (success && reconnectAttempt != 0) { + getLogger().info("Re-established connection to server"); + reconnectAttempt = 0; + } + + } +} diff --git a/uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java b/uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java index 31ec7658ee..26f3dff1a2 100644 --- a/uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java +++ b/uitest/src/com/vaadin/tests/requesthandlers/CommunicationError.java @@ -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); |