📄 presenceupdatehandler.java
字号:
/**
* $RCSfile: PresenceUpdateHandler.java,v $
* $Revision: 3125 $
* $Date: 2005-11-30 15:14:14 -0300 (Wed, 30 Nov 2005) $
*
* Copyright (C) 2008 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, or a commercial license
* agreement with Jive.
*/
package org.jivesoftware.openfire.handler;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.Lock;
/**
* Implements the presence protocol. Clients use this protocol to
* update presence and roster information.
* <p/>
* The handler must properly detect the presence type, update the user's roster,
* and inform presence subscribers of the session's updated presence
* status. Presence serves many purposes in Jabber so this handler will
* likely be the most complex of all handlers in the server.
* <p/>
* There are four basic types of presence updates:
* <ul>
* <li>Simple presence updates - addressed to the server (or to address), these updates
* are properly addressed by the server, and multicast to
* interested subscribers on the user's roster. An empty, missing,
* or "unavailable" type attribute indicates a simple update (there
* is no "available" type although it should be accepted by the server.
* <li>Directed presence updates - addressed to particular jabber entities,
* these presence updates are properly addressed and directly delivered
* to the entity without broadcast to roster subscribers. Any update type
* is possible except those reserved for subscription requests.
* <li>Subscription requests - these updates request presence subscription
* status changes. Such requests always affect the roster. The server must:
* <ul>
* <li>update the roster with the proper subscriber info
* <li>push the roster changes to the user
* <li>forward the update to the correct parties.
* </ul>
* The valid types include "subscribe", "subscribed", "unsubscribed",
* and "unsubscribe".
* <li>XMPPServer probes - Provides a mechanism for servers to query the presence
* status of users on another server. This allows users to immediately
* know the presence status of users when they come online rather than way
* for a presence update broadcast from the other server or tracking them
* as they are received. Requires S2S capabilities.
* </ul>
*
* @author Iain Shigeoka
*/
public class PresenceUpdateHandler extends BasicModule implements ChannelHandler, ClusterEventListener {
public static final String PRESENCE_CACHE_NAME = "Directed Presences";
/**
* Keeps track of entities that sent directed presences to other entities. In this map
* we keep track of every directed presence no matter if the recipient was hosted in
* this JVM or another cluster node.
*
* Key: sender, Value: list of DirectedPresences
*/
private Cache<String, Collection<DirectedPresence>> directedPresencesCache;
/**
* Same as the directedPresencesCache but only keeps directed presences sent from
* users connected to this JVM.
*/
private Map<String, Collection<DirectedPresence>> localDirectedPresences;
private RoutingTable routingTable;
private RosterManager rosterManager;
private XMPPServer localServer;
private PresenceManager presenceManager;
private PacketDeliverer deliverer;
private OfflineMessageStore messageStore;
private SessionManager sessionManager;
private UserManager userManager;
public PresenceUpdateHandler() {
super("Presence update handler");
localDirectedPresences = new ConcurrentHashMap<String, Collection<DirectedPresence>>();
}
public void process(Packet packet) throws UnauthorizedException, PacketException {
process((Presence) packet, sessionManager.getSession(packet.getFrom()));
}
private void process(Presence presence, ClientSession session) throws UnauthorizedException, PacketException {
try {
Presence.Type type = presence.getType();
// Available
if (type == null) {
if (session != null && session.getStatus() == Session.STATUS_CLOSED) {
Log.warn("Rejected available presence: " + presence + " - " + session);
return;
}
broadcastUpdate(presence.createCopy());
if (session != null) {
session.setPresence(presence);
if (!session.isInitialized()) {
initSession(session);
session.setInitialized(true);
}
}
// Notify the presence manager that the user is now available. The manager may
// remove the last presence status sent by the user when he went offline.
presenceManager.userAvailable(presence);
}
else if (Presence.Type.unavailable == type) {
broadcastUpdate(presence.createCopy());
broadcastUnavailableForDirectedPresences(presence);
if (session != null) {
session.setPresence(presence);
}
// Notify the presence manager that the user is now unavailable. The manager may
// save the last presence status sent by the user and keep track when the user
// went offline.
presenceManager.userUnavailable(presence);
}
else {
presence = presence.createCopy();
if (session != null) {
presence.setFrom(new JID(null, session.getServerName(), null, true));
presence.setTo(session.getAddress());
}
else {
JID sender = presence.getFrom();
presence.setFrom(presence.getTo());
presence.setTo(sender);
}
presence.setError(PacketError.Condition.bad_request);
deliverer.deliver(presence);
}
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error") + ". Triggered by packet: " + presence, e);
}
}
/**
* Handle presence updates that affect roster subscriptions.
*
* @param presence The presence presence to handle
* @throws PacketException if the packet is null or the packet could not be routed.
*/
public void process(Presence presence) throws PacketException {
try {
process((Packet)presence);
}
catch (UnauthorizedException e) {
try {
LocalSession session = (LocalSession) sessionManager.getSession(presence.getFrom());
presence = presence.createCopy();
if (session != null) {
presence.setFrom(new JID(null, session.getServerName(), null, true));
presence.setTo(session.getAddress());
}
else {
JID sender = presence.getFrom();
presence.setFrom(presence.getTo());
presence.setTo(sender);
}
presence.setError(PacketError.Condition.not_authorized);
deliverer.deliver(presence);
}
catch (Exception err) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), err);
}
}
}
/**
* A session that has transitioned to available status must be initialized.
* This includes:
* <ul>
* <li>Sending all offline presence subscription requests</li>
* <li>Sending offline messages</li>
* </ul>
*
* @param session The session being updated
* @throws UserNotFoundException If the user being updated does not exist
*/
private void initSession(ClientSession session) throws UserNotFoundException {
// Only user sessions need to be authenticated
if (userManager.isRegisteredUser(session.getAddress().getNode())) {
String username = session.getAddress().getNode();
// Send pending subscription requests to user if roster service is enabled
if (RosterManager.isRosterServiceEnabled()) {
Roster roster = rosterManager.getRoster(username);
for (RosterItem item : roster.getRosterItems()) {
if (item.getRecvStatus() == RosterItem.RECV_SUBSCRIBE) {
session.process(createSubscribePresence(item.getJid(),
new JID(session.getAddress().toBareJID()), true));
} else if (item.getRecvStatus() == RosterItem.RECV_UNSUBSCRIBE) {
session.process(createSubscribePresence(item.getJid(),
new JID(session.getAddress().toBareJID()), false));
}
if (item.getSubStatus() == RosterItem.SUB_TO
|| item.getSubStatus() == RosterItem.SUB_BOTH) {
presenceManager.probePresence(session.getAddress(), item.getJid());
}
}
}
if (session.canFloodOfflineMessages()) {
// deliver offline messages if any
Collection<OfflineMessage> messages = messageStore.getMessages(username, true);
for (Message message : messages) {
session.process(message);
}
}
}
}
public Presence createSubscribePresence(JID senderAddress, JID targetJID, boolean isSubscribe) {
Presence presence = new Presence();
presence.setFrom(senderAddress);
presence.setTo(targetJID);
if (isSubscribe) {
presence.setType(Presence.Type.subscribe);
}
else {
presence.setType(Presence.Type.unsubscribe);
}
return presence;
}
/**
* Broadcast the given update to all subscribers. We need to:
* <ul>
* <li>Query the roster table for subscribers</li>
* <li>Iterate through the list and send the update to each subscriber</li>
* </ul>
* <p/>
* Is there a safe way to cache the query results while maintaining
* integrity with roster changes?
*
* @param update The update to broadcast
*/
private void broadcastUpdate(Presence update) {
if (update.getFrom() == null) {
return;
}
if (localServer.isLocal(update.getFrom())) {
// Do nothing if roster service is disabled
if (!RosterManager.isRosterServiceEnabled()) {
return;
}
// Local updates can simply run through the roster of the local user
String name = update.getFrom().getNode();
try {
if (name != null && !"".equals(name)) {
Roster roster = rosterManager.getRoster(name);
roster.broadcastPresence(update);
}
}
catch (UserNotFoundException e) {
Log.warn("Presence being sent from unknown user " + name, e);
}
catch (PacketException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
else {
// Foreign updates will do a reverse lookup of entries in rosters
// on the server
Log.warn("Presence requested from server "
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -