/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2017 Pentaho Corporation.. All rights reserved. */ package org.pentaho.mantle.client.commands; import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting; import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.Timer; import org.pentaho.gwt.widgets.client.dialogs.SessionExpiredDialog; /** * Shows a session expired dialog in a top frame. * Won't work on the screens missing the mantle application. */ public class SessionExpiredCommand extends AbstractCommand { private static final String ZERO = "0"; //10 seconds by default private Integer pollingInterval = 10000; private static final String SESSION_EXPIRY = "session-expiry"; private static final String SERVER_TIME = "server-time"; private static final String CLIENT_TIME_OFFSET = "client-time-offset"; private static Timer timer; @Override protected void performOperation() { this.performOperation( true ); } @Override protected void performOperation( boolean feedback ) { //Reset and reinitialize timer once per page load if ( timer != null ) { timer.cancel(); timer = null; } //Calculate and set client/server time desynchronization offset once per page load setClientTimeOffset(); performCheck(); } /** * A timer loop */ private void performCheck() { final int nextCheckShift = getNextCheckShift(); if ( nextCheckShift < 0 ) { new SessionExpiredDialog().center(); } else { timer = new Timer() { @Override public void run() { performCheck(); } }; timer.schedule( nextCheckShift ); } } public Integer getPollingInterval() { return pollingInterval; } public void setPollingInterval( final Integer pollingInterval ) { this.pollingInterval = pollingInterval; } /** * If the session is expired returns a negative value. * If the session is not expired returns a time left before expiration. * If the cookie is not set returns a default polling interval. * * @return time shift for the next check */ @VisibleForTesting protected int getNextCheckShift() { final String sessionExpiry = getCookie( SESSION_EXPIRY ); final String clientTimeOffset = getCookie( CLIENT_TIME_OFFSET ); //A cookie is not set if ( sessionExpiry != null && clientTimeOffset != null ) { try { final long timeLeft = Long.parseLong( sessionExpiry ) - getClientTime() + Long.parseLong( clientTimeOffset ); if ( timeLeft <= 0 ) { //Session is expired return -1; //do not overflow } else if ( timeLeft < Integer.MAX_VALUE ) { //do not overflow //Not expired return (int) ( timeLeft ); } } catch ( NumberFormatException e ) { //wrong value in a cookie } } //No cookie - use a default interval return getPollingInterval(); } @VisibleForTesting protected long getClientTime() { return System.currentTimeMillis(); } /** * Don't be fooled with a System.currentTimeMillis() - in GWT it's a client side time. To eliminate a possible client * clock desynchronization effect we need to calculate an offset between client and server time. * Also there always will be a small positive offset equal to a time between the page load nad servlet invocation. * <p> * In case of any unpredicted situation the offset is set to 0. */ @VisibleForTesting protected void setClientTimeOffset() { final String serverTime = getCookie( SERVER_TIME ); if ( serverTime != null ) { try { //Could be negative final long timeOffset = getClientTime() - Long.parseLong( serverTime ); setCookie( CLIENT_TIME_OFFSET, String.valueOf( timeOffset ) ); } catch ( NumberFormatException e ) { setCookie( CLIENT_TIME_OFFSET, ZERO ); } } else { setCookie( CLIENT_TIME_OFFSET, ZERO ); } } @VisibleForTesting protected String getCookie( final String name ) { return Cookies.getCookie( name ); } @VisibleForTesting protected void setCookie( final String name, final String value ) { Cookies.setCookie( name, value ); } }