/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2006 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.multiplexer.net.http;
import org.jivesoftware.multiplexer.*;
import org.jivesoftware.multiplexer.spi.ClientFailoverDeliverer;
import org.dom4j.Element;
import org.dom4j.DocumentHelper;
import java.util.*;
/**
* A session represents a serious of interactions with an XMPP client sending packets using the HTTP
* Binding protocol specified in
* <a href="http://www.xmpp.org/extensions/xep-0124.html">XEP-0124</a>. A session can have several
* client connections open simultaneously while awaiting packets bound for the client from the
* server.
*
* @author Alexander Wenckus
*/
public class HttpSession extends Session {
private int wait;
private int hold = -1000;
private String language;
private final Queue<HttpConnection> connectionQueue = new LinkedList<HttpConnection>();
private final List<Element> pendingElements = new ArrayList<Element>();
private boolean isSecure;
private int maxPollingInterval;
private long lastPoll = -1;
private Set<SessionListener> listeners = new HashSet<SessionListener>();
private boolean isClosed;
private int inactivityTimeout;
protected HttpSession(String serverName, String streamID) {
super(serverName, null, streamID);
}
void addConnection(HttpConnection connection, boolean isPoll) throws HttpBindException,
HttpConnectionClosedException
{
if(connection == null) {
throw new IllegalArgumentException("Connection cannot be null.");
}
if(isPoll) {
checkPollingInterval();
}
if(isSecure && !connection.isSecure()) {
throw new HttpBindException("Session was started from secure connection, all " +
"connections on this session must be secured.", false, 403);
}
connection.setSession(this);
if (pendingElements.size() > 0) {
String deliverable = createDeliverable(pendingElements);
pendingElements.clear();
fireConnectionOpened(connection);
connection.deliverBody(deliverable);
fireConnectionClosed(connection);
}
else {
// With this connection we need to check if we will have too many connections open,
// closing any extras.
while (hold > 0 && connectionQueue.size() >= hold) {
HttpConnection toClose = connectionQueue.remove();
toClose.close();
fireConnectionClosed(toClose);
}
connectionQueue.offer(connection);
fireConnectionOpened(connection);
}
}
private void fireConnectionOpened(HttpConnection connection) {
Collection<SessionListener> listeners =
new HashSet<SessionListener>(this.listeners);
for(SessionListener listener : listeners) {
listener.connectionOpened(this, connection);
}
}
private void checkPollingInterval() throws HttpBindException {
long time = System.currentTimeMillis();
if(lastPoll > 0 && ((time - lastPoll) / 1000) < maxPollingInterval) {
throw new HttpBindException("Too frequent polling minimum interval is "
+ maxPollingInterval + ", current interval " + ((lastPoll - time) / 1000),
true, 403);
}
lastPoll = time;
}
public String getAvailableStreamFeatures() {
return null;
}
public synchronized void close() {
close(false);
}
public synchronized void close(boolean isServerShuttingDown) {
if(isClosed) {
return;
}
isClosed = true;
if(pendingElements.size() > 0) {
failDelivery();
}
Collection<SessionListener> listeners =
new HashSet<SessionListener>(this.listeners);
this.listeners.clear();
for(SessionListener listener : listeners) {
listener.sessionClosed(this);
}
}
private void failDelivery() {
ClientFailoverDeliverer deliverer = new ClientFailoverDeliverer();
deliverer.setStreamID(getStreamID());
for(Element element : pendingElements) {
deliverer.deliver(element);
}
pendingElements.clear();
}
public synchronized boolean isClosed() {
return isClosed;
}
public synchronized void deliver(Element stanza) {
String deliverable = createDeliverable(Arrays.asList(stanza));
boolean delivered = false;
while(!delivered && connectionQueue.size() > 0) {
HttpConnection connection = connectionQueue.remove();
try {
connection.deliverBody(deliverable);
delivered = true;
fireConnectionClosed(connection);
}
catch (HttpConnectionClosedException e) {
/* Connection was closed, try the next one */
}
}
if(!delivered) {
pendingElements.add(stanza);
}
}
private void fireConnectionClosed(HttpConnection connection) {
Collection<SessionListener> listeners =
new HashSet<SessionListener>(this.listeners);
for(SessionListener listener : listeners) {
listener.connectionClosed(this, connection);
}
}
private String createDeliverable(Collection<Element> elements) {
Element body = DocumentHelper.createElement("body");
body.addNamespace("", "http://jabber.org/protocol/httpbind");
for(Element child : elements) {
child = child.createCopy();
child.setParent(null);
body.add(child);
}
return body.asXML();
}
/**
* This attribute specifies the longest time (in seconds) that the connection manager is allowed
* to wait before responding to any request during the session. This enables the client to
* prevent its TCP connection from expiring due to inactivity, as well as to limit the delay
* before it discovers any network failure.
*
* @param wait the longest time it is permissible to wait for a response.
*/
public void setWait(int wait) {
this.wait = wait;
}
/**
* This attribute specifies the longest time (in seconds) that the connection manager is allowed
* to wait before responding to any request during the session. This enables the client to
* prevent its TCP connection from expiring due to inactivity, as well as to limit the delay
* before it discovers any network failure.
*
* @return the longest time it is permissible to wait for a response.
*/
public int getWait() {
return wait;
}
/**
* This attribute specifies the maximum number of requests the connection manager is allowed
* to keep waiting at any one time during the session. (For example, if a constrained client
* is unable to keep open more than two HTTP connections to the same HTTP server simultaneously,
* then it SHOULD specify a value of "1".)
*
* @param hold the maximum number of simultaneous waiting requests.
*
*/
public void setHold(int hold) {
this.hold = hold;
}
/**
* This attribute specifies the maximum number of requests the connection manager is allowed
* to keep waiting at any one time during the session. (For example, if a constrained client
* is unable to keep open more than two HTTP connections to the same HTTP server simultaneously,
* then it SHOULD specify a value of "1".)
*
* @return the maximum number of simultaneous waiting requests
*/
public int getHold() {
return hold;
}
public void setLanaguage(String language) {
this.language = language;
}
public String getLanguage() {
return language;
}
/**
* Sets the max interval within which a client can send polling requests. If more than one
* request occurs in the interval the session will be terminated.
*
* @param maxPollingInterval time in seconds a client needs to wait before sending polls to the
* server, a negative <i>int</i> indicates that there is no limit.
*/
public void setMaxPollingInterval(int maxPollingInterval) {
this.maxPollingInterval = maxPollingInterval;
}
/**
* Sets whether the initial request on the session was secure.
*
* @param isSecure true if the initial request was secure and false if it wasn't.
*/
protected void setSecure(boolean isSecure) {
this.isSecure = isSecure;
}
/**
* Returns true if all connections on this session should be secured, and false if
* they should not.
*
* @return true if all connections on this session should be secured, and false if
* they should not.
*/
public boolean isSecure() {
return isSecure;
}
public void addSessionCloseListener(SessionListener listener) {
listeners.add(listener);
}
public void removeSessionCloseListener(SessionListener listener) {
listeners.remove(listener);
}
public void setInactivityTimeout(int inactivityTimeout) {
this.inactivityTimeout = inactivityTimeout;
}
public int getInactivityTimeout() {
return inactivityTimeout;
}
public int getConnectionCount() {
return connectionQueue.size();
}
}