// Copyright 2010 Henrik Paul
//
// 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.github.wolfie.sessionguard.client.ui;
import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
import com.vaadin.terminal.gwt.client.ui.VNotification;
public class VSessionGuard extends Widget implements Paintable {
public static final String A_TIMEOUT_SECS_INT = "timeout";
public static final String A_WARNING_PERIOD_MINS_INT = "mins";
public static final String A_TIMEOUT_MSG_XHTML_STRING = "xhtml";
public static final String A_KEEPALIVE_BOOL = "keepalive";
public static final String V_PING_BOOL = "ping";
/** A minute in milliseconds */
private static final int MINUTE = 60000; // legibility ftw
private static final int MINUTES = MINUTE; // legibility ftw
/** The client side widget identifier */
protected String paintableId;
/** Reference to the server connection object. */
protected ApplicationConnection client;
private int timeoutMins;
private int warningPeriod;
private String xhtmlMessage;
private boolean keepalive;
private final Timer timer = new Timer() {
@Override
public void run() {
if (!keepalive) {
final VNotification notification = new VNotification(-1);
updateMinutes(notification, warningPeriod);
new Timer() {
int minutesLeft = warningPeriod;
@Override
public void run() {
if (notification.isShowing()) {
/*
* to make absolutely sure that we don't try to kill the session
* before the session is absolutely dead, count one minute extra.
*/
if (minutesLeft >= 0) {
// refresh the message with an updated minute-count
minutesLeft--;
updateMinutes(notification, minutesLeft);
} else {
/*
* the session should have ended by now (since we counted one
* minute extra), so Vaadin should show a session timeout
* message when trying to update the component.
*/
ping(true);
cancel();
notification.hide();
}
} else {
// the notification was hidden, so we'll count that as "activity";
// ping the session.
cancel();
ping(true);
}
}
}.scheduleRepeating(MINUTE);
} else {
// keepalive - ping!
ping(true);
}
}
private void updateMinutes(final VNotification notification,
final int minutesLeft) {
notification.show(getXhtmlMessage(xhtmlMessage, minutesLeft),
VNotification.CENTERED_TOP, "warning");
}
private String getXhtmlMessage(final String xhtmlMessage, final int minutes) {
return xhtmlMessage.replace("_",
String.valueOf(minutes >= 0 ? minutes : 0));
}
};
public VSessionGuard() {
setElement(Document.get().createDivElement());
if (BrowserInfo.get().isIE6()) {
getElement().getStyle().setProperty("overflow", "hidden");
getElement().getStyle().setProperty("height", "0");
}
}
public void updateFromUIDL(final UIDL uidl, final ApplicationConnection client) {
this.client = client;
paintableId = uidl.getId();
if (client.updateComponent(this, uidl, true)) {
return;
}
if (uidl.hasAttribute(A_TIMEOUT_SECS_INT)) {
timeoutMins = uidl.getIntAttribute(A_TIMEOUT_SECS_INT) / 60;
updateTimer();
}
if (uidl.hasAttribute(A_WARNING_PERIOD_MINS_INT)) {
warningPeriod = uidl.getIntAttribute(A_WARNING_PERIOD_MINS_INT);
updateTimer();
}
if (uidl.hasAttribute(A_TIMEOUT_MSG_XHTML_STRING)) {
xhtmlMessage = uidl.getStringAttribute(A_TIMEOUT_MSG_XHTML_STRING);
}
if (uidl.hasAttribute(A_KEEPALIVE_BOOL)) {
keepalive = uidl.getBooleanAttribute(A_KEEPALIVE_BOOL);
updateTimer();
}
if (uidl.hasVariable(V_PING_BOOL)) {
// we don't actually need to do anything with this. It doesn't matter
// whether this value comes through or not.
}
/*
* Never ping here immediately - when another component sends something to
* the server, this will also be sent. Once the server-side gets this value,
* it will refresh this, and we'll know that the session isn't about to end.
*/
ping(false);
}
private void ping(final boolean now) {
client.updateVariable(paintableId, "ping", true, now);
}
private void updateTimer() {
if (!keepalive && 0 < timeoutMins && timeoutMins > warningPeriod) {
// re-schedule the timeout
timer.schedule((timeoutMins - warningPeriod) * MINUTES);
} else if (keepalive) {
/*
* always launch keepalive only half a minute before the session is to
* end. Half a minute, since one minute is the shortest session, and we
* don't to update all the time (i.e. 0 delay)
*/
timer.schedule(Long.valueOf(Math.round((timeoutMins - 0.5) * MINUTES))
.intValue());
} else {
// timeout was <= 0, which doesn't make sense, or timeout <=
// warningPeriod, which is just plain stupid. Abort countdown.
timer.cancel();
}
}
@Override
protected void onDetach() {
super.onDetach();
timer.cancel();
}
}