]> source.dussan.org Git - vaadin-framework.git/commitdiff
When closeIdleUIs is true, close open UIs only after all UIs have been idle for longe... 36/236/8
authorJohannes Dahlström <johannesd@vaadin.com>
Thu, 1 Nov 2012 13:10:40 +0000 (15:10 +0200)
committerVaadin Code Review <review@vaadin.com>
Tue, 20 Nov 2012 11:25:37 +0000 (11:25 +0000)
* Rename closeIdleUIs to closeIdleSessions, isIdleUICleanupEnabled to isCloseIdleSessions (#10252)
* Close the whole session at the same time as well
* Move UI lastUidlRequestTime to VaadinSession lastRequestTimestamp (#10253)
* Rename VaadinSession lastRequestTime to lastRequestDuration, totalSessionTime to cumulativeRequestDuration (#10253)
* Rename UI lastHeartbeatTime to lastHeartbeatTimestamp
* Show "Session Expired" notification when a heartbeat fails because of this

Change-Id: If3d4bd9add9995f435c29812eec00b3a3a92a6e6

12 files changed:
WebContent/WEB-INF/web.xml
client/src/com/vaadin/client/ApplicationConfiguration.java
client/src/com/vaadin/client/ApplicationConnection.java
server/src/com/vaadin/server/AbstractCommunicationManager.java
server/src/com/vaadin/server/BootstrapHandler.java
server/src/com/vaadin/server/Constants.java
server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
server/src/com/vaadin/server/DeploymentConfiguration.java
server/src/com/vaadin/server/RequestTimer.java
server/src/com/vaadin/server/VaadinSession.java
server/src/com/vaadin/ui/UI.java
uitest/src/com/vaadin/tests/application/DeploymentConfigurationTest.html

index 820eb2cbce2be831f72abcc6d12a4c3c1643338b..cd1f0a75cc6e94a84df5ec76865ffa62ca93cec1 100644 (file)
@@ -61,7 +61,7 @@
                        <param-value>3601</param-value>
                </init-param>
                <init-param>
-                       <param-name>closeIdleUIs</param-name>
+                       <param-name>closeIdleSessions</param-name>
                        <param-value>true</param-value>
                </init-param>
                <init-param>
index 4159b382112fb400b16be1787ff22ca5dbeae461..1f35c408aeaa20b0b931a7c93fc695d712db0753 100644 (file)
@@ -199,6 +199,7 @@ public class ApplicationConfiguration implements EntryPoint {
     private boolean standalone;
     private ErrorMessage communicationError;
     private ErrorMessage authorizationError;
+    private ErrorMessage sessionExpiredError;
     private int heartbeatInterval;
 
     private HashMap<Integer, String> unknownComponents;
@@ -314,6 +315,10 @@ public class ApplicationConfiguration implements EntryPoint {
         return authorizationError;
     }
 
+    public ErrorMessage getSessionExpiredError() {
+        return sessionExpiredError;
+    }
+
     /**
      * Reads the configuration values defined by the bootstrap javascript.
      */
@@ -353,6 +358,7 @@ public class ApplicationConfiguration implements EntryPoint {
 
         communicationError = jsoConfiguration.getConfigError("comErrMsg");
         authorizationError = jsoConfiguration.getConfigError("authErrMsg");
+        sessionExpiredError = jsoConfiguration.getConfigError("sessExpMsg");
 
         // boostrap sets initPending to false if it has sent the browser details
         if (jsoConfiguration.getConfigBoolean("initPending") == Boolean.FALSE) {
index 23906f0e022c79d686ffc4d19bf4bcdb5e5b04c9..9133e3dfc52af6eec6091a13d2b0c14ba3b64e1b 100644 (file)
@@ -960,9 +960,7 @@ public class ApplicationConnection {
      */
     protected void showCommunicationError(String details, int statusCode) {
         VConsole.error("Communication error: " + details);
-        ErrorMessage communicationError = configuration.getCommunicationError();
-        showError(details, communicationError.getCaption(),
-                communicationError.getMessage(), communicationError.getUrl());
+        showError(details, configuration.getCommunicationError());
     }
 
     /**
@@ -973,9 +971,31 @@ public class ApplicationConnection {
      */
     protected void showAuthenticationError(String details) {
         VConsole.error("Authentication error: " + details);
-        ErrorMessage authorizationError = configuration.getAuthorizationError();
-        showError(details, authorizationError.getCaption(),
-                authorizationError.getMessage(), authorizationError.getUrl());
+        showError(details, configuration.getAuthorizationError());
+    }
+
+    /**
+     * Shows the session expiration notification.
+     * 
+     * @param details
+     *            Optional details for debugging.
+     */
+    protected void showSessionExpiredError(String details) {
+        VConsole.error("Session expired: " + details);
+        showError(details, configuration.getSessionExpiredError());
+    }
+
+    /**
+     * Shows an error notification.
+     * 
+     * @param details
+     *            Optional details for debugging.
+     * @param message
+     *            An ErrorMessage describing the error.
+     */
+    protected void showError(String details, ErrorMessage message) {
+        showError(details, message.getCaption(), message.getMessage(),
+                message.getUrl());
     }
 
     /**
@@ -1002,9 +1022,11 @@ public class ApplicationConnection {
         if (html.length() > 0) {
 
             // Add error description
-            html.append("<br/><p><I style=\"font-size:0.7em\">");
-            html.append(details);
-            html.append("</I></p>");
+            if (details != null) {
+                html.append("<p><i style=\"font-size:0.7em\">");
+                html.append(details);
+                html.append("</i></p>");
+            }
 
             VNotification n = VNotification.createNotification(1000 * 60 * 45,
                     uIConnector.getWidget());
@@ -1442,32 +1464,11 @@ public class ApplicationConnection {
                 if (meta != null) {
                     if (meta.containsKey("appError")) {
                         ValueMap error = meta.getValueMap("appError");
-                        String html = "";
-                        if (error.containsKey("caption")
-                                && error.getString("caption") != null) {
-                            html += "<h1>" + error.getAsString("caption")
-                                    + "</h1>";
-                        }
-                        if (error.containsKey("message")
-                                && error.getString("message") != null) {
-                            html += "<p>" + error.getAsString("message")
-                                    + "</p>";
-                        }
-                        String url = null;
-                        if (error.containsKey("url")) {
-                            url = error.getString("url");
-                        }
 
-                        if (html.length() != 0) {
-                            /* 45 min */
-                            VNotification n = VNotification.createNotification(
-                                    1000 * 60 * 45, uIConnector.getWidget());
-                            n.addEventListener(new NotificationRedirect(url));
-                            n.show(html, VNotification.CENTERED_TOP,
-                                    VNotification.STYLE_SYSTEM);
-                        } else {
-                            redirect(url);
-                        }
+                        showError(null, error.getString("caption"),
+                                error.getString("message"),
+                                error.getString("url"));
+
                         applicationRunning = false;
                     }
                     if (validatingLayouts) {
@@ -3027,7 +3028,7 @@ public class ApplicationConnection {
      * <p>
      * Heartbeat requests are used to inform the server that the client-side is
      * still alive. If the client page is closed or the connection lost, the
-     * server will eventually close the inactive Root.
+     * server will eventually close the inactive UI.
      * <p>
      * <b>TODO</b>: Improved error handling, like in doUidlRequest().
      * 
@@ -3051,16 +3052,17 @@ public class ApplicationConnection {
                     // TODO Permit retry in some error situations
                     VConsole.log("Heartbeat response OK");
                     scheduleHeartbeat();
+                } else if (status == Response.SC_GONE) {
+                    showSessionExpiredError(null);
                 } else {
-                    VConsole.error("Heartbeat request failed with status code "
+                    VConsole.error("Failed sending heartbeat to server. Error code: "
                             + status);
                 }
             }
 
             @Override
             public void onError(Request request, Throwable exception) {
-                VConsole.error("Heartbeat request resulted in exception");
-                VConsole.error(exception);
+                VConsole.error("Exception sending heartbeat: " + exception);
             }
         };
 
index 0655ad6e740f311034e3d04a6ef84a654188c4a3..dc987530bd7ea3048700b43875809222d0722087 100644 (file)
@@ -583,8 +583,7 @@ public abstract class AbstractCommunicationManager implements Serializable {
                 return;
             }
 
-            // Keep the UI alive
-            uI.setLastUidlRequestTime(System.currentTimeMillis());
+            session.setLastRequestTimestamp(System.currentTimeMillis());
 
             // Change all variables based on request parameters
             if (!handleVariables(request, response, callback, session, uI)) {
@@ -1333,7 +1332,7 @@ public abstract class AbstractCommunicationManager implements Serializable {
      */
     private void writePerformanceData(final PrintWriter outWriter) {
         outWriter.write(String.format(", \"timings\":[%d, %d]",
-                session.getTotalSessionTime(), session.getLastRequestTime()));
+                session.getCumulativeRequestDuration(), session.getLastRequestDuration()));
     }
 
     private void legacyPaint(PaintTarget paintTarget,
@@ -2798,7 +2797,7 @@ public abstract class AbstractCommunicationManager implements Serializable {
             // null-check below handles this as well
         }
         if (ui != null) {
-            ui.setLastHeartbeatTime(System.currentTimeMillis());
+            ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
         } else {
             response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
         }
index 36a09253d9b3a18b5b30a24e22ae35cd8c8747a8..1ab439035178bfcbb028ad80103d30e75328d215 100644 (file)
@@ -434,6 +434,15 @@ public abstract class BootstrapHandler implements RequestHandler {
             authErrMsg.put("url", systemMessages.getAuthenticationErrorURL());
 
             appConfig.put("authErrMsg", authErrMsg);
+
+            JSONObject sessExpMsg = new JSONObject();
+            sessExpMsg
+                    .put("caption", systemMessages.getSessionExpiredCaption());
+            sessExpMsg
+                    .put("message", systemMessages.getSessionExpiredMessage());
+            sessExpMsg.put("url", systemMessages.getSessionExpiredURL());
+
+            appConfig.put("sessExpMsg", sessExpMsg);
         }
 
         // getStaticFileLocation documented to never end with a slash
index b6bfcc0495584657b14c2a4a7a36788f2f47fc42..faf24b4bce97f25e31bb35b7464e970bcca37add 100644 (file)
@@ -62,7 +62,7 @@ public interface Constants {
     static final String SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION = "disable-xsrf-protection";
     static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime";
     static final String SERVLET_PARAMETER_HEARTBEAT_INTERVAL = "heartbeatInterval";
-    static final String SERVLET_PARAMETER_CLOSE_IDLE_UIS = "closeIdleUIs";
+    static final String SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS = "closeIdleSessions";
     static final String SERVLET_PARAMETER_UI_PROVIDER = "UIProvider";
 
     // Configurable parameter names
index 23392e59a818f8ff0078720db9c72e298bb828dd..13218f6e45904e895bb2fc2dae3423de8b51b7cf 100644 (file)
@@ -32,7 +32,7 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
     private boolean xsrfProtectionEnabled;
     private int resourceCacheTime;
     private int heartbeatInterval;
-    private boolean idleUICleanupEnabled;
+    private boolean closeIdleSessions;
     private final Class<?> systemPropertyBaseClass;
 
     /**
@@ -54,7 +54,7 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
         checkXsrfProtection();
         checkResourceCacheTime();
         checkHeartbeatInterval();
-        checkIdleUICleanup();
+        checkCloseIdleSessions();
     }
 
     @Override
@@ -168,8 +168,8 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
     }
 
     @Override
-    public boolean isIdleUICleanupEnabled() {
-        return idleUICleanupEnabled;
+    public boolean isCloseIdleSessions() {
+        return closeIdleSessions;
     }
 
     /**
@@ -225,10 +225,10 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
         }
     }
 
-    private void checkIdleUICleanup() {
-        idleUICleanupEnabled = getApplicationOrSystemProperty(
-                Constants.SERVLET_PARAMETER_CLOSE_IDLE_UIS, "false").equals(
-                "true");
+    private void checkCloseIdleSessions() {
+        closeIdleSessions = getApplicationOrSystemProperty(
+                Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS, "false")
+                .equals("true");
     }
 
     private Logger getLogger() {
index 09405d6004be58ff658d10bac7eef444ccd3f21c..65f039388363ca8a566ce5f15775d15954330884 100644 (file)
@@ -58,19 +58,24 @@ public interface DeploymentConfiguration extends Serializable {
     public int getHeartbeatInterval();
 
     /**
-     * Returns whether UIs that have no other activity than heartbeat requests
-     * should be removed from the session after they have been idle the maximum
-     * inactivity time enforced by the session.
+     * Returns whether a session should be closed when all its open UIs have
+     * been idle for longer than its configured maximum inactivity time.
+     * <p>
+     * A UI is idle if it is open on the client side but has no activity other
+     * than heartbeat requests. If {@code isCloseIdleSessions() == false},
+     * heartbeat requests cause the session to stay open for as long as there
+     * are open UIs on the client side. If it is {@code true}, the session is
+     * eventually closed if the open UIs do not have any user interaction.
      * 
      * @see WrappedSession#getMaxInactiveInterval()
      * 
      * @since 7.0.0
      * 
-     * @return True if UIs receiving only heartbeat requests are eventually
-     *         removed; false if heartbeat requests extend UI lifetime
-     *         indefinitely.
+     * @return True if UIs and sessions receiving only heartbeat requests are
+     *         eventually closed; false if heartbeat requests extend UI and
+     *         session lifetime indefinitely.
      */
-    public boolean isIdleUICleanupEnabled();
+    public boolean isCloseIdleSessions();
 
     /**
      * Gets the properties configured for the deployment, e.g. as init
index 26a56896658f46ad699ec67864dd09ce4539b7a7..bfe5362afefba6b78f610659004a35fe949f0565 100644 (file)
@@ -50,6 +50,6 @@ public class RequestTimer implements Serializable {
 
         // The timings must be stored in the context, since a new
         // RequestTimer is created for every request.
-        context.setLastRequestTime(time);
+        context.setLastRequestDuration(time);
     }
 }
index d2eb1a436a6db93767efa4151cfff9e6889c77f6..08b408fa28b108a1a173fba41440f2d7dce6d04c 100644 (file)
@@ -113,9 +113,11 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
 
     private AbstractCommunicationManager communicationManager;
 
-    private long totalSessionTime = 0;
+    private long cumulativeRequestDuration = 0;
 
-    private long lastRequestTime = -1;
+    private long lastRequestDuration = -1;
+
+    private long lastRequestTimestamp = System.currentTimeMillis();
 
     private transient WrappedSession session;
 
@@ -167,10 +169,11 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
     }
 
     /**
-     * @return The total time spent servicing requests in this session.
+     * @return The total time spent servicing requests in this session, in
+     *         milliseconds.
      */
-    public long getTotalSessionTime() {
-        return totalSessionTime;
+    public long getCumulativeRequestDuration() {
+        return cumulativeRequestDuration;
     }
 
     /**
@@ -178,18 +181,31 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      * the total time spent servicing requests in this session.
      * 
      * @param time
-     *            the time spent in the last request.
+     *            The time spent in the last request, in milliseconds.
+     */
+    public void setLastRequestDuration(long time) {
+        lastRequestDuration = time;
+        cumulativeRequestDuration += time;
+    }
+
+    /**
+     * @return The time spent servicing the last request in this session, in
+     *         milliseconds.
      */
-    public void setLastRequestTime(long time) {
-        lastRequestTime = time;
-        totalSessionTime += time;
+    public long getLastRequestDuration() {
+        return lastRequestDuration;
     }
 
     /**
-     * @return the time spent servicing the last request in this session.
+     * Sets the time when the last UIDL request was serviced in this session.
+     * 
+     * @param timestamp
+     *            The time when the last request was handled, in milliseconds
+     *            since the epoch.
+     * 
      */
-    public long getLastRequestTime() {
-        return lastRequestTime;
+    public void setLastRequestTimestamp(long timestamp) {
+        lastRequestTimestamp = timestamp;
     }
 
     /**
@@ -596,11 +612,17 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      */
     @Deprecated
     public void cleanupInactiveUIs() {
-        for (UI ui : new ArrayList<UI>(uIs.values())) {
-            if (!isUIAlive(ui)) {
-                cleanupUI(ui);
-                getLogger().fine(
-                        "Closed UI #" + ui.getUIId() + " due to inactivity");
+        if (getUidlRequestTimeout() >= 0
+                && System.currentTimeMillis() - lastRequestTimestamp > 1000 * getUidlRequestTimeout()) {
+            close();
+        } else {
+            for (UI ui : new ArrayList<UI>(uIs.values())) {
+                if (!isUIAlive(ui)) {
+                    cleanupUI(ui);
+                    getLogger()
+                            .fine("Closed UI #" + ui.getUIId()
+                                    + " due to inactivity");
+                }
             }
         }
     }
@@ -655,7 +677,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      * otherwise heartbeat requests are enough to extend UI lifetime
      * indefinitely.
      * 
-     * @see DeploymentConfiguration#isIdleUICleanupEnabled()
+     * @see DeploymentConfiguration#isCloseIdleSessions()
      * @see #getHeartbeatTimeout()
      * @see #cleanupInactiveUIs()
      * 
@@ -665,7 +687,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      *         timeout never occurs.
      */
     protected int getUidlRequestTimeout() {
-        return configuration.isIdleUICleanupEnabled() ? getSession()
+        return configuration.isCloseIdleSessions() ? getSession()
                 .getMaxInactiveInterval() : -1;
     }
 
@@ -688,11 +710,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
     protected boolean isUIAlive(UI ui) {
         long now = System.currentTimeMillis();
         if (getHeartbeatTimeout() >= 0
-                && now - ui.getLastHeartbeatTime() > 1000 * getHeartbeatTimeout()) {
-            return false;
-        }
-        if (getUidlRequestTimeout() >= 0
-                && now - ui.getLastUidlRequestTime() > 1000 * getUidlRequestTimeout()) {
+                && now - ui.getLastHeartbeatTimestamp() > 1000 * getHeartbeatTimeout()) {
             return false;
         }
         return true;
index 342983e8b88cacffc8c2a3ab01af493b8f027134..83936a74786b14bf911922ed532448f370f2ef14 100644 (file)
@@ -172,9 +172,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements
      * current time whenever the application receives a heartbeat or UIDL
      * request from the client for this UI.
      */
-    private long lastHeartbeat = System.currentTimeMillis();
-
-    private long lastUidlRequest = System.currentTimeMillis();
+    private long lastHeartbeatTimestamp = System.currentTimeMillis();
 
     /**
      * Creates a new empty UI without a caption. The content of the UI must be
@@ -942,43 +940,29 @@ public abstract class UI extends AbstractSingleComponentContainer implements
     }
 
     /**
-     * Returns the timestamp (milliseconds since the epoch) of the last received
-     * heartbeat for this UI.
+     * Returns the timestamp of the last received heartbeat for this UI.
      * 
      * @see #heartbeat()
      * @see VaadinSession#cleanupInactiveUIs()
      * 
-     * @return The time the last heartbeat request occurred.
-     */
-    public long getLastHeartbeatTime() {
-        return lastHeartbeat;
-    }
-
-    /**
-     * Returns the timestamp (milliseconds since the epoch) of the last received
-     * UIDL request for this UI.
-     * 
-     * @return
+     * @return The time the last heartbeat request occurred, in milliseconds
+     *         since the epoch.
      */
-    public long getLastUidlRequestTime() {
-        return lastUidlRequest;
+    public long getLastHeartbeatTimestamp() {
+        return lastHeartbeatTimestamp;
     }
 
     /**
      * Sets the last heartbeat request timestamp for this UI. Called by the
      * framework whenever the application receives a valid heartbeat request for
      * this UI.
+     * 
+     * @param lastHeartbeat
+     *            The time the last heartbeat request occurred, in milliseconds
+     *            since the epoch.
      */
-    public void setLastHeartbeatTime(long lastHeartbeat) {
-        this.lastHeartbeat = lastHeartbeat;
-    }
-
-    /**
-     * Sets the last UIDL request timestamp for this UI. Called by the framework
-     * whenever the application receives a valid UIDL request for this UI.
-     */
-    public void setLastUidlRequestTime(long lastUidlRequest) {
-        this.lastUidlRequest = lastUidlRequest;
+    public void setLastHeartbeatTimestamp(long lastHeartbeat) {
+        lastHeartbeatTimestamp = lastHeartbeat;
     }
 
     /**
index f5ad0987aa9668c627fde236c41c731ff97802c7..2bdb7c79a59e77ce4446c3b65fc54d08c1e762fe 100644 (file)
@@ -18,7 +18,7 @@
 </tr>
 <tr>
        <td>assertTextPresent</td>
-       <td>exact:closeIdleUIs: true</td>
+       <td>exact:closeIdleSessions: true</td>
        <td></td>
 </tr>
 <tr>