Commit 88d0f396 authored by Derek DeMoro's avatar Derek DeMoro Committed by derek

Moving SessionManager to singleton.


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@565 b35dd754-fafc-0310-a699-88a17e54d16e
parent 97156ac7
...@@ -11,195 +11,747 @@ ...@@ -11,195 +11,747 @@
package org.jivesoftware.messenger; package org.jivesoftware.messenger;
import org.jivesoftware.messenger.auth.UnauthorizedException; import java.util.*;
import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap;
import java.util.Collection; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamException;
import org.jivesoftware.messenger.audit.AuditStreamIDFactory;
import org.jivesoftware.messenger.auth.UnauthorizedException;
import org.jivesoftware.messenger.container.Container;
import org.jivesoftware.messenger.container.TrackInfo;
import org.jivesoftware.messenger.spi.BasicStreamIDFactory;
import org.jivesoftware.messenger.spi.SessionImpl;
import org.jivesoftware.messenger.user.UserManager;
import org.jivesoftware.messenger.user.UserNotFoundException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
/** /**
* Manages the sessions associated with an account. The information * Manages the sessions associated with an account. The information
* maintained by the Session manager is entirely transient and does * maintained by the Session manager is entirely transient and does
* not need to be preserved between server restarts. * not need to be preserved between server restarts.
* *
* @author Iain Shigeoka * @author Derek DeMoro
*/ */
public interface SessionManager { public class SessionManager implements ConnectionCloseListener {
private int sessionCount = 0;
final int NEVER_KICK = -1; final int NEVER_KICK = -1;
/**
* Creates a new session for the given connection. Session managers will provide
* their own sessions through this factory.
*
* @param conn The connection to wrap a session around
* @return The session wrapped around the given connection
* @throws UnauthorizedException if the caller doesn't have permission to access this resource
*/
public Session createSession(Connection conn) throws UnauthorizedException;
/** public XMPPServer server;
* Change the priority of a session associated with the sender. public PacketRouter router;
* public PacketTransporter transporter;
* @param sender The sender who's session just changed priority private String serverName;
* @param priority The new priority for the session private XMPPAddress serverAddress;
*/ public UserManager userManager;
public void changePriority(XMPPAddress sender, int priority) throws UnauthorizedException; private int conflictLimit;
private Random randomResource = new Random();
/** private static SessionManager singleton;
* Retrieve the best route to deliver packets to this session private static final Object LOCK = new Object();
* given the recipient jid. If no active routes exist, this method
* returns a reference to itself (the account can store the packet).
* A null recipient chooses the default active route for this account
* if one exists. If the recipient can't be reached by this account
* (wrong account) an exception is thrown.
*
* @param recipient The recipient ID to send to or null to select the default route
* @return The XMPPAddress best suited to use for delivery to the recipient
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/
public Session getBestRoute(XMPPAddress recipient) throws UnauthorizedException;
/** /**
* Determines if there is an active (reachable session) with the given address. * Returns the singleton instance of <CODE>SessionManagerImpl</CODE>,
* <p/>
* creating it if necessary.
* <p/>
* <p/>
* *
* @param route The address being checked for an active session * @return the singleton instance of <Code>SessionManagerImpl</CODE>
* @return True if there is a session with this address
*/ */
public boolean isActiveRoute(XMPPAddress route); public static SessionManager getInstance() {
// Synchronize on LOCK to ensure that we don't end up creating
// two singletons.
synchronized (LOCK) {
if (null == singleton) {
SessionManager manager = new SessionManager();
singleton = manager;
return manager;
}
}
return singleton;
}
private SessionManager() {
if (JiveGlobals.getBooleanProperty("xmpp.audit.active")) {
streamIDFactory = new AuditStreamIDFactory();
}
else {
streamIDFactory = new BasicStreamIDFactory();
}
String conflictLimitProp = JiveGlobals.getProperty("xmpp.session.conflict-limit");
if (conflictLimitProp == null) {
conflictLimit = 0;
JiveGlobals.setProperty("xmpp.session.conflict-limit", Integer.toString(conflictLimit));
}
else {
try {
conflictLimit = Integer.parseInt(conflictLimitProp);
}
catch (NumberFormatException e) {
conflictLimit = 0;
JiveGlobals.setProperty("xmpp.session.conflict-limit", Integer.toString(conflictLimit));
}
}
}
/** /**
* <p>Obtain the session associated with the XMPPAddress.</p> * The standard Jive reader/writer lock to synchronize access to
* <p>All sessions will have a resource so addresses without * the session set.
* resources are sure to throw a not found exception.</p>
*
* @param address The address of the session you'd like to receive
* @return The session corresponding to the given address
* @throws UnauthorizedException If caller doesn't have permission to access this method
* @throws SessionNotFoundException If there is no session matching the given address
*/ */
public Session getSession(XMPPAddress address) private ReadWriteLock sessionLock = new ReentrantReadWriteLock();
throws UnauthorizedException, SessionNotFoundException;
/** /**
* Returns the Collection of all sessions on the server. * Map of priority ordered SessionMap objects with username (toLowerCase) as key.
* * The map and its contents should NOT be persisted to disk.
* @return the Collection of all sessions.
*/ */
public Collection<Session> getSessions(); private Map<String, SessionMap> sessions = new ConcurrentHashMap<String, SessionMap>();
/** /**
* Returns a Collection of all sessions on the server given the * <p>Session manager must maintain the routing table as sessions are added and
* specified ResultFilter. * removed.</p>
*
* @param filter the result filter to apply to the search.
* @return the Collection of all sessions.
*/ */
public Collection<Session> getSessions(SessionResultFilter filter); public RoutingTable routingTable;
/** /**
* <p>Obtain an iterator of all anonymous sessions on the server.</p> * The standard Jive reader/writer lock to synchronize access to
* * the anonymous session set.
* @return An iterator over the anonynmous sessions (never null)
*/ */
public Iterator getAnonymousSessions(); private ReadWriteLock anonymousSessionLock = new ReentrantReadWriteLock();
/** /**
* <p>Obtain an iterator of all sessions for a given user on the server.</p> * Map of anonymous server sessions. They need to be treated separately as they
* * have no associated user, and don't follow the normal routing rules for
* @param username The name of the user that owns the sessions * priority based fall over.
* @return An iterator over the sessions (never null)
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/ */
public Collection<Session> getSessions(String username) throws UnauthorizedException; private HashMap anonymousSessions = new HashMap();
/** /**
* <p>Obtain a count of the number of sessions on the server, including unauthenticated * Simple data structure to track sessions for a single user (tracked by resource
* sessions (raw connections).</p> * and priority).
*
* @return The total number of sessions on the server including unauthenticated sessions
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/ */
public int getTotalSessionCount() throws UnauthorizedException; private class SessionMap {
private HashMap resources = new HashMap();
private LinkedList priorityList = new LinkedList();
/**
* Add a session to the manager.
*
* @param session
*/
void addSession(Session session) {
String resource = session.getAddress().getResource();
resources.put(resource, session);
Presence presence = session.getPresence();
int priority = presence == null ? 0 : presence.getPriority();
sortSession(resource, priority);
}
/**
* Sorts the session into the list based on priority
*
* @param resource The resource corresponding to the session to sort
* @param priority The priority to use for sorting
*/
private void sortSession(String resource, int priority) {
if (priorityList.size() > 0) {
Iterator iter = priorityList.iterator();
for (int i = 0; iter.hasNext(); i++) {
Session sess = (Session)resources.get(iter.next());
if (sess.getPresence().getPriority() <= priority) {
priorityList.add(i, resource);
break;
}
}
}
if (!priorityList.contains(resource)) {
priorityList.addLast(resource);
}
}
/**
* Change the priority of a session associated with the sender.
*
* @param sender The sender who's session just changed priority
* @param priority The new priority for the session
*/
public void changePriority(XMPPAddress sender, int priority) {
String resource = sender.getResource();
if (resources.containsKey(resource)) {
priorityList.remove(resource);
sortSession(resource, priority);
}
}
/**
* Remove a session from the manager.
*
* @param session The session to remove
*/
void removeSession(Session session) {
String resource = session.getAddress().getResource();
resources.remove(resource);
priorityList.remove(resource);
}
/**
* Gets the session for the given resource.
*
* @param resource The resource describing the particular session
* @return The session for that resource or null if none found (use getDefaultSession() to obtain default)
*/
Session getSession(String resource) {
return (Session)resources.get(resource);
}
/**
* Checks to see if a session for the given resource exists.
*
* @param resource The resource of the session we're checking
* @return True if we have a session corresponding to that resource
*/
boolean hasSession(String resource) {
return resources.containsKey(resource);
}
/**
* Returns the default session for the user based on presence
* priority.
*
* @return The default session for the user.
*/
Session getDefaultSession() {
if (priorityList.isEmpty()) {
return null;
}
// return (Session) resources.get(priorityList.getFirst());
Session s = (Session)resources.get(priorityList.getFirst());
return s;
}
/**
* Determines if this map is empty or not.
*
* @return True if the map contains no entries
*/
boolean isEmpty() {
return resources.isEmpty();
}
/**
* Broadcast to all resources for the given user
*
* @param packet
*/
private void broadcast(XMPPPacket packet) throws
UnauthorizedException, PacketException, XMLStreamException {
Iterator entries = resources.values().iterator();
while (entries.hasNext()) {
Session session = (Session)entries.next();
packet.setRecipient(session.getAddress());
session.getConnection().deliver(packet);
}
}
/**
* Create an iterator over all sessions for the user.
* We create a new list to generate the iterator so other threads
* may safely alter the session map without affecting the iterator.
*
* @return An iterator of all sessions
*/
public Iterator getSessions() {
LinkedList list = new LinkedList();
Iterator entries = resources.values().iterator();
while (entries.hasNext()) {
list.add(entries.next());
}
return list.iterator();
}
}
private StreamIDFactory streamIDFactory;
public Session createSession(Connection conn) throws UnauthorizedException {
if (serverName == null) {
throw new UnauthorizedException("Server not initialized");
}
StreamID id = streamIDFactory.createStreamID();
Session session = new SessionImpl(serverName, conn, id);
conn.init(session);
conn.registerCloseListener(this, session);
return session;
}
/** /**
* <p>Obtain a count of the number of authenticated sessions on the server.</p> * Add a new session to be managed.
*
* @return The total number of active sessions on the server
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/ */
public int getSessionCount() throws UnauthorizedException; public boolean addSession(Session session) {
boolean success = false;
sessionLock.writeLock().lock();
String username = session.getAddress().getName().toLowerCase();
SessionMap resources = null;
try {
resources = (SessionMap)sessions.get(username);
if (resources == null) {
resources = new SessionMap();
sessions.put(username, resources);
}
resources.addSession(session);
// Register to recieve close notification on this session so we can
// remove its route from the sessions set. We hand the session back
// to ourselves in the message.
session.getConnection().registerCloseListener(this, session);
success = true;
}
catch (UnauthorizedException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
sessionLock.writeLock().unlock();
}
if (success) {
Session defaultSession = resources.getDefaultSession();
routingTable.addRoute(new XMPPAddress(defaultSession.getAddress().getNamePrep(),
defaultSession.getAddress().getHostPrep(), ""), defaultSession);
routingTable.addRoute(session.getAddress(), session);
}
return success;
}
/** /**
* <p>Obtain a count of the number of anonymous sessions on the server.</p> * Change the priority of a session associated with the sender.
* *
* @return The total number of anonymous sessions on the server * @param sender The sender who's session just changed priority
* @throws UnauthorizedException If caller doesn't have permission to access this method * @param priority The new priority for the session
*/ */
public int getAnonymousSessionCount() throws UnauthorizedException; public void changePriority(XMPPAddress sender, int priority) {
String username = sender.getName().toLowerCase();
sessionLock.writeLock().lock();
try {
SessionMap resources = (SessionMap)sessions.get(username);
if (resources == null) {
return;
}
resources.changePriority(sender, priority);
// Get the session with highest priority
Session defaultSession = resources.getDefaultSession();
// Update the route to the bareJID with the session with highest priority
routingTable.addRoute(new XMPPAddress(defaultSession.getAddress().getNamePrep(),
defaultSession.getAddress().getHostPrep(), ""), defaultSession);
}
finally {
sessionLock.writeLock().unlock();
}
}
/** /**
* <p>Obtain a count of the number of active sessions for an authenticated user on the server.</p> * Retrieve the best route to deliver packets to this session
* * given the recipient jid. If no active routes exist, this method
* @param username The name of the user who owns the sessions to be counted * returns a reference to itself (the account can store the packet).
* @return The total number of active sessions on the server for a particular user * A null recipient chooses the default active route for this account
* @throws UnauthorizedException If caller doesn't have permission to access this method * if one exists. If the recipient can't be reached by this account
*/ * (wrong account) an exception is thrown.
public int getSessionCount(String username) throws UnauthorizedException;
/**
* Obtain an iterator of all user names for logged in (active) users.
* <pre><code>
* Iterator itr = sessionManager.getSessionUsers();
* while (itr.hasNext()){
* String name = (String)itr.next();
* Iterator sessItr = sessionManager.getSessions(name);
* while (sessItr.hasNext()){
* //..
* }
* }
* </code></pre>
* *
* @return An iterator over the sessions (never null) * @param recipient The recipient ID to send to or null to select the default route
* @return The XMPPAddress best suited to use for delivery to the recipient
*/ */
public Iterator getSessionUsers() throws UnauthorizedException; public Session getBestRoute(XMPPAddress recipient) {
Session session = null;
String resource = recipient.getResource();
String username = recipient.getName();
if (username == null || "".equals(username)) {
if (resource != null) {
anonymousSessionLock.readLock().lock();
try {
session = (Session)anonymousSessions.get(resource);
}
finally {
anonymousSessionLock.readLock().unlock();
}
}
}
else {
username = username.toLowerCase();
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
if (resource == null) {
session = sessionMap.getDefaultSession();
}
else {
session = sessionMap.getSession(resource);
if (session == null) {
session = sessionMap.getDefaultSession();
}
}
}
}
finally {
sessionLock.readLock().unlock();
}
}
return session;
}
public boolean isActiveRoute(XMPPAddress route) {
boolean hasRoute = false;
String resource = route.getResource();
String username = route.getName();
if (username == null || "".equals(username)) {
if (resource != null) {
anonymousSessionLock.readLock().lock();
try {
hasRoute = anonymousSessions.containsKey(resource);
}
finally {
anonymousSessionLock.readLock().unlock();
}
}
}
else {
username = username.toLowerCase();
Session session = null;
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
if (resource == null) {
hasRoute = !sessionMap.isEmpty();
}
else {
if (sessionMap.hasSession(resource)) {
session = sessionMap.getSession(resource);
}
}
}
}
finally {
sessionLock.readLock().unlock();
}
// Makes sure the session is still active
// Must occur outside of the lock since validation can cause
// the socket to close - deadlocking on session removal
if (session != null && !session.getConnection().isClosed()) {
hasRoute = session.getConnection().validate();
}
}
return hasRoute;
}
public Session getSession(XMPPAddress address)
throws UnauthorizedException, SessionNotFoundException {
Session session = null;
String resource = address.getResource();
if (resource == null) {
throw new SessionNotFoundException();
}
String username = address.getName();
if (username == null || "".equals(username)) {
anonymousSessionLock.readLock().lock();
try {
session = (Session)anonymousSessions.get(resource);
}
finally {
anonymousSessionLock.readLock().unlock();
}
}
else {
username = username.toLowerCase();
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
session = sessionMap.getSession(resource);
}
}
finally {
sessionLock.readLock().unlock();
}
}
if (session == null) {
throw new SessionNotFoundException();
}
return session;
}
public Collection<Session> getSessions() {
List<Session> allSessions = new ArrayList<Session>();
copyUserSessions(allSessions);
copyAnonSessions(allSessions);
return allSessions;
}
public Collection<Session> getSessions(SessionResultFilter filter) {
List<Session> results = new ArrayList<Session>();
if (filter != null) {
// Grab all the possible matching sessions by user
if (filter.getUsername() == null) {
// No user id filtering
copyAnonSessions(results);
copyUserSessions(results);
}
else {
try {
copyUserSessions(userManager.getUser(filter.getUsername()).getUsername(),
results);
}
catch (UserNotFoundException e) {
}
}
Date createMin = filter.getCreationDateRangeMin();
Date createMax = filter.getCreationDateRangeMax();
Date activityMin = filter.getLastActivityDateRangeMin();
Date activityMax = filter.getLastActivityDateRangeMax();
// Now we have a copy of the references so we can spend some time
// doing the rest of the filtering without locking out session access
// so let's iterate and filter each session one by one
List<Session> filteredResults = new ArrayList<Session>();
for (Session session : results) {
// Now filter on creation date if needed
if (createMin != null || createMax != null) {
if (!isBetweenDates(session.getCreationDate(), createMin, createMax)) {
session = null;
}
}
// Now filter on activity date if needed
if ((activityMin != null || activityMax != null) && session != null) {
if (!isBetweenDates(session.getLastActiveDate(), activityMin, activityMax)) {
session = null;
}
}
if (session != null) {
if (!isBetweenPacketCount(session.getNumClientPackets(),
filter.getClientPacketRangeMin(),
filter.getClientPacketRangeMax())) {
session = null;
}
}
if (session != null) {
if (!isBetweenPacketCount(session.getNumServerPackets(),
filter.getServerPacketRangeMin(),
filter.getServerPacketRangeMax())) {
session = null;
}
}
if (session != null) {
filteredResults.add(session);
}
}
// Sort list.
Collections.sort(filteredResults, filter.getSortComparator());
int maxResults = filter.getNumResults();
if (maxResults == SessionResultFilter.NO_RESULT_LIMIT) {
maxResults = filteredResults.size();
}
// Now generate the final list. I believe it's faster to to build up a new
// list than it is to remove items from head and tail of the sorted tree
List<Session> finalResults = new ArrayList<Session>();
int startIndex = filter.getStartIndex();
Iterator<Session> sortedIter = filteredResults.iterator();
for (int i = 0; sortedIter.hasNext() && finalResults.size() < maxResults; i++) {
Session result = sortedIter.next();
if (i >= startIndex) {
finalResults.add(result);
}
}
return finalResults;
}
return results;
}
/** /**
* <p>Sends a server message to all connected users.</p> * <p>Determines if the given date is before the min date, or after the max date.</p>
* <p>This is very useful for making server-wide announcements such as * <p>The check is complicated somewhat by the fact that min can be null indicating
* shutdown alerts.</p> * no earlier date, and max can be null indicating no upper limit.</p>
* *
* @param subject The subject for the message or null for no subject * @param date The date to check
* @param body The body of the message (required) * @param min The date must be after min, or any if min is null
* @throws UnauthorizedException If caller doesn't have permission to access this method * @param max The date must be before max, or any if max is null
* @return True if the date is between min and max
*/ */
public void sendServerMessage(String subject, String body) private boolean isBetweenDates(Date date, Date min, Date max) {
throws UnauthorizedException; boolean between = true;
if (min != null) {
if (date.before(min)) {
between = false;
}
}
if (max != null && between) {
if (date.after(max)) {
between = false;
}
}
return between;
}
/** /**
* <p>Sends a server message to a user (all sessions) or resource (one session).</p> * <p>Determines if the given count is before the min count, or after the max count.</p>
* <p>This is very useful for announcing administration changes that will affect * <p>The check is complicated somewhat by the fact that min or max
* a particular session or user. If the address is to user@server.com then the message * can be SessionResultFilter.NO_PACKET_LIMIT indicating no limit.</p>
* is sent to all connected session for the specified user. If the address includes the
* resource name (e.g. user@server.com/resource) the message is only sent to a session
* logged into that resource.</p>
* *
* @param address The address of the user or session to receive the message, or null to send to all users * @param count The count to check
* @param subject The subject for the message or null for no subject * @param min The count must be over min, or any if min is SessionResultFilter.NO_PACKET_LIMIT
* @param body The body of the message * @param max The count must be under max, or any if max is SessionResultFilter.NO_PACKET_LIMIT
* @throws UnauthorizedException If caller doesn't have permission to access this method * @return True if the count is between min and max
*/ */
public void sendServerMessage(XMPPAddress address, String subject, String body) private boolean isBetweenPacketCount(long count, long min, long max) {
throws UnauthorizedException, SessionNotFoundException; boolean between = true;
if (min != SessionResultFilter.NO_PACKET_LIMIT) {
if (count < min) {
between = false;
}
}
if (max != SessionResultFilter.NO_PACKET_LIMIT && between) {
if (count > max) {
between = false;
}
}
return between;
}
private void copyAnonSessions(List sessions) {
// Add anonymous sessions
anonymousSessionLock.readLock().lock();
try {
Iterator sessionItr = anonymousSessions.values().iterator();
while (sessionItr.hasNext()) {
sessions.add(sessionItr.next());
}
}
finally {
anonymousSessionLock.readLock().unlock();
}
}
private void copyUserSessions(List sessions) {
// Get a copy of the sessions from all users
sessionLock.readLock().lock();
try {
Iterator users = getSessionUsers();
while (users.hasNext()) {
Collection<Session> usrSessions = getSessions((String)users.next());
for (Session session : usrSessions) {
sessions.add(session);
}
}
}
finally {
sessionLock.readLock().unlock();
}
}
private void copyUserSessions(String username, List sessionList) {
// Get a copy of the sessions from all users
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
Iterator sessionItr = sessionMap.getSessions();
while (sessionItr.hasNext()) {
sessionList.add(sessionItr.next());
}
}
}
finally {
sessionLock.readLock().unlock();
}
}
public Iterator getAnonymousSessions() {
return Arrays.asList(anonymousSessions.values().toArray()).iterator();
}
public Collection<Session> getSessions(String username) {
List<Session> sessionList = new ArrayList<Session>();
if (username != null) {
copyUserSessions(username, sessionList);
}
return sessionList;
}
public int getTotalSessionCount() throws UnauthorizedException {
return sessionCount;
}
public int getSessionCount() throws UnauthorizedException {
int sessionCount = 0;
Iterator users = getSessionUsers();
while (users.hasNext()) {
sessionCount += getSessionCount((String)users.next());
}
sessionCount += anonymousSessions.size();
return sessionCount;
}
public int getAnonymousSessionCount() throws UnauthorizedException {
return anonymousSessions.size();
}
public int getSessionCount(String username) throws UnauthorizedException {
int sessionCount = 0;
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
sessionCount = sessionMap.resources.size();
}
}
finally {
sessionLock.readLock().unlock();
}
return sessionCount;
}
public Iterator getSessionUsers() {
return Arrays.asList(sessions.keySet().toArray()).iterator();
}
/** /**
* Broadcasts the given data to all connected sessions. Excellent * Broadcasts the given data to all connected sessions. Excellent
* for server administration messages. * for server administration messages.
* *
* @param packet The packet to be broadcast * @param packet The packet to be broadcast
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/ */
public void broadcast(XMPPPacket packet) throws UnauthorizedException, public void broadcast(XMPPPacket packet) throws
PacketException, XMLStreamException; UnauthorizedException, PacketException, XMLStreamException {
sessionLock.readLock().lock();
try {
Iterator values = sessions.values().iterator();
while (values.hasNext()) {
((SessionMap)values.next()).broadcast(packet);
}
}
finally {
sessionLock.readLock().unlock();
}
anonymousSessionLock.readLock().lock();
try {
Iterator values = anonymousSessions.values().iterator();
while (values.hasNext()) {
((Session)values.next()).getConnection().deliver(packet);
}
}
finally {
anonymousSessionLock.readLock().unlock();
}
}
/** /**
* Broadcasts the given data to all connected sessions for a particular * Broadcasts the given data to all connected sessions for a particular
...@@ -207,39 +759,233 @@ public interface SessionManager { ...@@ -207,39 +759,233 @@ public interface SessionManager {
* roster pushes. * roster pushes.
* *
* @param packet The packet to be broadcast * @param packet The packet to be broadcast
* @throws UnauthorizedException If caller doesn't have permission to access this method
*/ */
public void userBroadcast(String username, XMPPPacket packet) throws public void userBroadcast(String username, XMPPPacket packet) throws
UnauthorizedException, PacketException, XMLStreamException; UnauthorizedException, PacketException, XMLStreamException {
sessionLock.readLock().lock();
try {
SessionMap sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
sessionMap.broadcast(packet);
}
}
finally {
sessionLock.readLock().unlock();
}
}
/** /**
* <p>Obtain the number of conflicts a session can conflict with new sessions before * TODO Requires better error checking to ensure the session count is maintained properly (removal actually does remove)
* being kicked off.</p>
* <p>A kick limit of 0 means old sessions will be kicked immediately when new,
* authenticated sessions want it's resource. Conversely, a kick limit of
* SessionManager.NEVER_KICK will cause the server to never kick off an
* existing resource.</p>
* *
* @return The kick limit for the server * @param session
* @throws UnauthorizedException
*/ */
public int getConflictKickLimit(); public void removeSession(Session session) throws UnauthorizedException {
if (session == null) {
return;
}
SessionMap sessionMap = null;
if (anonymousSessions.containsValue(session)) {
anonymousSessionLock.writeLock().lock();
try {
anonymousSessions.remove(session.getAddress().getResource());
sessionCount--;
}
finally {
anonymousSessionLock.writeLock().unlock();
}
}
else {
if (session.getAddress() != null && session.getAddress().getName() != null) {
String username = session.getAddress().getName().toLowerCase();
sessionLock.writeLock().lock();
try {
sessionMap = (SessionMap)sessions.get(username);
if (sessionMap != null) {
sessionMap.removeSession(session);
sessionCount--;
if (sessionMap.isEmpty()) {
sessions.remove(username);
}
}
}
finally {
sessionLock.writeLock().unlock();
}
}
}
/** Presence presence = session.getPresence();
* <p>Set the number of conflicts a session can conflict with new sessions before if (presence == null || presence.isAvailable()) {
* being kicked off.</p> Presence offline = packetFactory.getPresence();
* <p>A kick limit of 0 means old sessions will be kicked immediately when new, offline.setOriginatingSession(session);
* authenticated sessions want it's resource. Conversely, a kick limit of offline.setSender(session.getAddress());
* SessionManager.NEVER_KICK will cause the server to never kick off an offline.setRecipient(new XMPPAddress(null, serverName, null));
* existing resource.</p> offline.setAvailable(false);
* router.route(offline);
* @param limit The new kick limit for the server }
*/ if (session.getAddress() != null && routingTable != null && !session.getAddress().isEmpty()) {
public void setConflictKickLimit(int limit) throws UnauthorizedException; routingTable.removeRoute(session.getAddress());
if (sessionMap != null) {
if (sessionMap.isEmpty()) {
// Remove the route for the session's BARE address
routingTable.removeRoute(new XMPPAddress(session.getAddress().getNamePrep(),
session.getAddress().getHostPrep(), ""));
}
else {
// Update the route for the session's BARE address
Session defaultSession = sessionMap.getDefaultSession();
routingTable.addRoute(new XMPPAddress(defaultSession.getAddress().getNamePrep(),
defaultSession.getAddress().getHostPrep(), ""), defaultSession);
}
}
}
}
public void addAnonymousSession(Session session) {
try {
session.getAddress().setResource(Integer.toHexString(randomResource.nextInt()));
anonymousSessionLock.writeLock().lock();
try {
anonymousSessions.put(session.getAddress().getResource(), session);
session.getConnection().registerCloseListener(this, session);
}
finally {
anonymousSessionLock.writeLock().unlock();
}
routingTable.addRoute(session.getAddress(), session);
}
catch (UnauthorizedException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
public int getConflictKickLimit() {
return conflictLimit;
}
public void setConflictKickLimit(int limit) {
conflictLimit = limit;
JiveGlobals.setProperty("xmpp.session.conflict-limit", Integer.toString(conflictLimit));
}
/** /**
* Add a new session with an anonymous login (no user account) * Handle a session that just closed.
* *
* @param session The session to add * @param handback The session that just closed
*/ */
public void addAnonymousSession(Session session); public void onConnectionClose(Object handback) {
try {
Session session = (Session)handback;
removeSession(session);
}
catch (UnauthorizedException e) {
// Do nothing
}
catch (Exception e) {
// Can't do anything about this problem...
Log.error(LocaleUtils.getLocalizedString("admin.error.close"), e);
}
}
public PacketFactory packetFactory;
protected TrackInfo getTrackInfo() {
TrackInfo trackInfo = new TrackInfo();
trackInfo.getTrackerClasses().put(XMPPServer.class, "server");
trackInfo.getTrackerClasses().put(PacketTransporter.class, "transporter");
trackInfo.getTrackerClasses().put(PacketRouter.class, "router");
trackInfo.getTrackerClasses().put(UserManager.class, "userManager");
trackInfo.getTrackerClasses().put(PacketFactory.class, "packetFactory");
trackInfo.getTrackerClasses().put(RoutingTable.class, "routingTable");
return trackInfo;
}
public void serviceAdded(Object service) {
if (service instanceof XMPPServer && server != null) {
serverName = server.getServerInfo().getName();
serverAddress = XMPPAddress.parseJID(serverName);
}
}
public void serviceRemoved(Object service) {
if (server == null) {
serverName = null;
}
}
public void initialize(Container container) {
if (JiveGlobals.getBooleanProperty("xmpp.audit.active")) {
streamIDFactory = new AuditStreamIDFactory();
}
else {
streamIDFactory = new BasicStreamIDFactory();
}
String conflictLimitProp = JiveGlobals.getProperty("xmpp.session.conflict-limit");
if (conflictLimitProp == null) {
conflictLimit = 0;
JiveGlobals.setProperty("xmpp.session.conflict-limit", Integer.toString(conflictLimit));
}
else {
try {
conflictLimit = Integer.parseInt(conflictLimitProp);
}
catch (NumberFormatException e) {
conflictLimit = 0;
JiveGlobals.setProperty("xmpp.session.conflict-limit", Integer.toString(conflictLimit));
}
}
}
public void sendServerMessage(String subject, String body) {
try {
sendServerMessage(null, subject, body);
}
catch (SessionNotFoundException e) {
}
}
public void sendServerMessage(XMPPAddress address, String subject, String body) throws SessionNotFoundException {
XMPPPacket packet = createServerMessage(subject, body);
try {
if (address == null || address.getName() == null || address.getName().length() < 1) {
broadcast(packet);
}
else if (address.getResource() == null || address.getResource().length() < 1) {
userBroadcast(address.getName(), packet);
}
else {
getSession(address).getConnection().deliver(packet);
}
}
catch (Exception e) {
}
}
private XMPPPacket createServerMessage(String subject, String body) {
Message message = packetFactory.getMessage();
message.setSender(serverAddress);
if (subject != null) {
message.setSubject(subject);
}
message.setBody(body);
return message;
}
public void stop() {
sendServerMessage(null, LocaleUtils.getLocalizedString("admin.shutdown.now"));
try {
for (Session session : getSessions()) {
try {
session.getConnection().close();
}
catch (Throwable t) {
}
}
}
catch (Exception e) {
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment