Browse Source

Server-side heartbeat-related public API (#9265)

tags/7.0.0.beta1
Johannes Dahlström 11 years ago
parent
commit
20b403146e
2 changed files with 152 additions and 9 deletions
  1. 49
    8
      server/src/com/vaadin/Application.java
  2. 103
    1
      server/src/com/vaadin/ui/Root.java

+ 49
- 8
server/src/com/vaadin/Application.java View File

@@ -31,6 +31,7 @@ import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
@@ -575,19 +576,19 @@ public class Application implements Terminal.ErrorListener, Serializable {

/**
* Ends the Application.
*
* <p>
* In effect this will cause the application stop returning any windows when
* asked. When the application is closed, its state is removed from the
* session and the browser window is redirected to the application logout
* url set with {@link #setLogoutURL(String)}. If the logout url has not
* been set, the browser window is reloaded and the application is
* restarted.
* </p>
* .
* asked. When the application is closed, close events are fired for its
* roots, its state is removed from the session, and the browser window is
* redirected to the application logout url set with
* {@link #setLogoutURL(String)}. If the logout url has not been set, the
* browser window is reloaded and the application is restarted.
*/
public void close() {
applicationIsRunning = false;
for (Root root : getRoots()) {
root.fireCloseEvent();
}
}

/**
@@ -2443,4 +2444,44 @@ public class Application implements Terminal.ErrorListener, Serializable {
public void modifyBootstrapResponse(BootstrapResponse response) {
eventRouter.fireEvent(response);
}

/**
* Removes all those roots from the application whose last heartbeat
* occurred more than {@link #getHeartbeatTimeout()} seconds ago. Close
* events are fired for the removed roots.
* <p>
* Called by the framework at the end of every request.
*
* @see Root.CloseEvent
* @see Root.CloseListener
* @see #getHeartbeatTimeout()
*
* @since 7.0.0
*/
public void closeInactiveRoots() {
long now = System.currentTimeMillis();
for (Iterator<Root> i = roots.values().iterator(); i.hasNext();) {
Root root = i.next();
if (now - root.getLastHeartbeat() > 1000 * getHeartbeatTimeout()) {
i.remove();
retainOnRefreshRoots.values().remove(root.getRootId());
root.fireCloseEvent();
}
}
}

/**
* Returns the number of seconds that must pass without a valid heartbeat or
* UIDL request being received from a root before that root is removed from
* the application. This is a lower bound; it might take longer to close an
* inactive root.
*
* @since 7.0.0
*
* @return The heartbeat timeout in seconds.
*/
public int getHeartbeatTimeout() {
// Permit three missed heartbeats before closing the root
return (int) (configuration.getHeartbeatInterval() * (3.1));
}
}

+ 103
- 1
server/src/com/vaadin/ui/Root.java View File

@@ -16,11 +16,13 @@

package com.vaadin.ui;

import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -46,7 +48,7 @@ import com.vaadin.terminal.Resource;
import com.vaadin.terminal.Vaadin6Component;
import com.vaadin.terminal.WrappedRequest;
import com.vaadin.terminal.WrappedRequest.BrowserDetails;
import com.vaadin.ui.Window.CloseListener;
import com.vaadin.tools.ReflectTools;

/**
* The topmost component in any component hierarchy. There is one root for every
@@ -388,6 +390,42 @@ public abstract class Root extends AbstractComponentContainer implements

}

/**
* Event fired when a Root is removed from the application.
*/
public static class CloseEvent extends Event {

private static final String CLOSE_EVENT_IDENTIFIER = "rootClose";

public CloseEvent(Root source) {
super(source);
}

public Root getRoot() {
return (Root) getSource();
}
}

/**
* Interface for listening {@link Root.CloseEvent root close events}.
*
*/
public interface CloseListener extends EventListener {

public static final Method closeMethod = ReflectTools.findMethod(
CloseListener.class, "click", CloseEvent.class);

/**
* Called when a CloseListener is notified of a CloseEvent.
* {@link Root#getCurrent()} returns <code>event.getRoot()</code> within
* this method.
*
* @param event
* The close event that was fired.
*/
public void close(CloseEvent event);
}

/**
* The application to which this root belongs
*/
@@ -436,6 +474,13 @@ public abstract class Root extends AbstractComponentContainer implements
}
};

/**
* Timestamp keeping track of the last heartbeat of this Root. Updated to
* the current time whenever the application receives a heartbeat or UIDL
* request from the client for this Root.
*/
private long lastHeartbeat = System.currentTimeMillis();

/**
* Creates a new empty root without a caption. This root will have a
* {@link VerticalLayout} with margins enabled as its content.
@@ -564,6 +609,16 @@ public abstract class Root extends AbstractComponentContainer implements
fireEvent(new ClickEvent(this, mouseDetails));
}

/**
* For internal use only.
*/
public void fireCloseEvent() {
Root current = Root.getCurrent();
Root.setCurrent(this);
fireEvent(new CloseEvent(this));
Root.setCurrent(current);
}

@Override
@SuppressWarnings("unchecked")
public void changeVariables(Object source, Map<String, Object> variables) {
@@ -1055,6 +1110,30 @@ public abstract class Root extends AbstractComponentContainer implements
listener);
}

/**
* Adds a close listener to the Root. The listener is called when the Root
* is removed from the application.
*
* @param listener
* The listener to add.
*/
public void addListener(CloseListener listener) {
addListener(CloseEvent.CLOSE_EVENT_IDENTIFIER, CloseEvent.class,
listener, CloseListener.closeMethod);
}

/**
* Removes a close listener from the Root if it has previously been added
* with {@link #addListener(ClickListener)}. Otherwise, has no effect.
*
* @param listener
* The listener to remove.
*/
public void removeListener(CloseListener listener) {
removeListener(CloseEvent.CLOSE_EVENT_IDENTIFIER, CloseEvent.class,
listener);
}

@Override
public boolean isConnectorEnabled() {
// TODO How can a Root be invisible? What does it mean?
@@ -1238,4 +1317,27 @@ public abstract class Root extends AbstractComponentContainer implements
getPage().showNotification(notification);
}

/**
* Returns the timestamp (millisecond since the epoch) of the last received
* heartbeat for this Root.
*
* @see #heartbeat()
* @see Application#closeInactiveRoots()
*
* @return The time
*/
public long getLastHeartbeat() {
return lastHeartbeat;
}

/**
* Updates the heartbeat timestamp of this Root to the current time. Called
* by the framework whenever the application receives a valid heartbeat or
* UIDL request for this Root.
*
* @see java.lang.System#currentTimeMillis()
*/
public void heartbeat() {
this.lastHeartbeat = System.currentTimeMillis();
}
}

Loading…
Cancel
Save