From aac352ab8ba7b7d9e4ace96939e424b2c4e02530 Mon Sep 17 00:00:00 2001 From: Manolo Carrasco Date: Wed, 14 May 2014 12:55:47 +0200 Subject: [PATCH] Make Hearbeat available to other modules (#13250) - When a mobile app goes online/offline we need to change Heartbeat interval and restart the schedule. - We also need to be notified about response status in order to show the appropriate offline UI, etc. Related with Issue #13250 and review https://dev.vaadin.com/review/#/c/3376/ Change-Id: I428501306e37fb8c2ee0ed6022a4c588bd8456db --- .../vaadin/client/ApplicationConnection.java | 67 ++++++++++++-- .../client/communication/Heartbeat.java | 89 +++++++++++-------- 2 files changed, 114 insertions(+), 42 deletions(-) diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 6dcc2abb44..694fc71060 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -1,6 +1,6 @@ /* * 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 @@ -43,6 +43,7 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.event.shared.HasHandlers; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.http.client.Request; import com.google.gwt.http.client.RequestBuilder; @@ -65,6 +66,7 @@ import com.google.gwt.user.client.Window.ClosingHandler; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.HasJavaScriptConnectorHelper; @@ -119,7 +121,7 @@ import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; * * Entry point classes (widgetsets) define onModuleLoad(). */ -public class ApplicationConnection { +public class ApplicationConnection implements HasHandlers { /** * Helper used to return two values when updating the connector hierarchy. @@ -341,6 +343,42 @@ public class ApplicationConnection { } + /** + * Event triggered when a XHR request has finished with the status code of + * the response. + * + * Useful for handlers observing network failures like online/off-line + * monitors. + */ + public static class ConnectionStatusEvent extends + GwtEvent { + private int status; + + public static interface ConnectionStatusHandler extends EventHandler { + public void onConnectionStatusChange(ConnectionStatusEvent event); + } + + public ConnectionStatusEvent(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + public final static Type TYPE = new Type(); + + @Override + public Type getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(ConnectionStatusHandler handler) { + handler.onConnectionStatusChange(this); + } + } + public static class ResponseHandlingStartedEvent extends ApplicationConnectionEvent { @@ -835,6 +873,8 @@ public class ApplicationConnection { - requestStartTime.getTime()) + "ms"); int statusCode = response.getStatusCode(); + // Notify network observers about response status + fireEvent(new ConnectionStatusEvent(statusCode)); switch (statusCode) { case 0: @@ -932,6 +972,7 @@ public class ApplicationConnection { } catch (RequestException e) { VConsole.error(e); endRequest(); + fireEvent(new ConnectionStatusEvent(0)); } } } @@ -1083,10 +1124,13 @@ public class ApplicationConnection { handleWhenCSSLoaded(jsonText, json); } }).schedule(50); - VConsole.log("Assuming CSS loading is not complete, " - + "postponing render phase. " - + "(.v-loading-indicator height == 0)"); - cssWaits++; + + // Show this message just once + if (cssWaits++ == 0) { + VConsole.log("Assuming CSS loading is not complete, " + + "postponing render phase. " + + "(.v-loading-indicator height == 0)"); + } } else { cssLoaded = true; handleReceivedJSONMessage(new Date(), jsonText, json); @@ -3464,6 +3508,11 @@ public class ApplicationConnection { return eventBus.addHandler(type, handler); } + @Override + public void fireEvent(GwtEvent event) { + eventBus.fireEvent(event); + } + /** * Calls {@link ComponentConnector#flush()} on the active connector. Does * nothing if there is no active (focused) connector. @@ -3558,4 +3607,10 @@ public class ApplicationConnection { return Logger.getLogger(ApplicationConnection.class.getName()); } + /** + * Returns the hearbeat instance. + */ + public Heartbeat getHeartbeat() { + return heartbeat; + } } diff --git a/client/src/com/vaadin/client/communication/Heartbeat.java b/client/src/com/vaadin/client/communication/Heartbeat.java index f2253f3faa..1ff0825f0e 100644 --- a/client/src/com/vaadin/client/communication/Heartbeat.java +++ b/client/src/com/vaadin/client/communication/Heartbeat.java @@ -25,6 +25,7 @@ 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; @@ -36,7 +37,6 @@ import com.vaadin.shared.ui.ui.UIConstants; */ public class Heartbeat { - private int interval = -1; private Timer timer = new Timer() { @Override public void run() { @@ -45,6 +45,8 @@ public class Heartbeat { }; private ApplicationConnection connection; + private String uri; + private int interval = -1; private static Logger getLogger() { return Logger.getLogger(Heartbeat.class.getName()); @@ -56,11 +58,16 @@ public class Heartbeat { * @param connection * the connection */ - public void init(ApplicationConnection connection) { - this.connection = connection; - interval = connection.getConfiguration().getHeartbeatInterval(); - setInterval(interval); - schedule(); + public void init(ApplicationConnection applicationConnection) { + connection = applicationConnection; + + setInterval(connection.getConfiguration().getHeartbeatInterval()); + + uri = ApplicationConnection.addGetParameters(connection + .translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX + + ApplicationConstants.HEARTBEAT_PATH + '/'), + UIConstants.UI_ID_PARAMETER + "=" + + connection.getConfiguration().getUIId()); connection.addHandler( ApplicationConnection.ApplicationStoppedEvent.TYPE, @@ -70,22 +77,15 @@ public class Heartbeat { public void onApplicationStopped( ApplicationStoppedEvent event) { setInterval(-1); - schedule(); } }); - } /** * Sends a heartbeat to the server */ public void send() { - final String uri = ApplicationConnection.addGetParameters( - getConnection().translateVaadinUri( - ApplicationConstants.APP_PROTOCOL_PREFIX - + ApplicationConstants.HEARTBEAT_PATH + '/'), - UIConstants.UI_ID_PARAMETER + "=" - + getConnection().getConfiguration().getUIId()); + timer.cancel(); final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); @@ -94,24 +94,41 @@ public class Heartbeat { @Override 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) { - // TODO Permit retry in some error situations getLogger().fine("Heartbeat response OK"); - schedule(); + } 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) { - // FIXME This should really do something else like send an - // event - getConnection().showSessionExpiredError(null); + connection.showSessionExpiredError(null); + // If session is expired break the loop + return; } else { getLogger().warning( "Failed sending heartbeat to server. Error code: " + status); } + + // Don't break the loop + schedule(); } @Override public void onError(Request request, Throwable exception) { - getLogger().severe("Exception sending heartbeat: " + exception); + getLogger().severe("Exception sending heartbeat: " + exception.getMessage()); + // Notify network observers about response status + connection.fireEvent(new ConnectionStatusEvent(0)); + // Don't break the loop + schedule(); } }; @@ -133,39 +150,39 @@ public class Heartbeat { return interval; } - /** - * sets the interval at which heartbeat requests are sent - * - * @param interval - * the new interval - */ - public void setInterval(int interval) { - this.interval = interval; - } - /** * Updates the schedule of the heartbeat to match the set interval. A * negative interval disables the heartbeat. */ public void schedule() { - if (getInterval() > 0) { + if (interval > 0) { getLogger() .fine("Scheduling heartbeat in " + interval + " seconds"); timer.schedule(interval * 1000); } else { - if (timer != null) { - getLogger().fine("Disabling heartbeat"); - timer.cancel(); - } + getLogger().fine("Disabling heartbeat"); + timer.cancel(); } - } /** * @return the application connection */ + @Deprecated protected ApplicationConnection getConnection() { return connection; } + /** + * Changes the heartbeatInterval in runtime and applies it. + * + * @param heartbeatInterval + * new interval in seconds. + */ + public void setInterval(int heartbeatInterval) { + getLogger().info( + "Setting hearbeat interval to " + heartbeatInterval + "sec."); + interval = heartbeatInterval; + schedule(); + } } -- 2.39.5