PresenceManagerImpl.java 23.2 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: PresenceManagerImpl.java,v $
 * $Revision: 3128 $
 * $Date: 2005-11-30 15:31:54 -0300 (Wed, 30 Nov 2005) $
 *
6
 * Copyright (C) 2004-2007 Jive Software. All rights reserved.
7 8 9 10 11
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

12
package org.jivesoftware.openfire.spi;
13 14 15

import org.dom4j.Document;
import org.dom4j.DocumentException;
16
import org.dom4j.DocumentHelper;
17
import org.jivesoftware.database.DbConnectionManager;
18
import org.jivesoftware.openfire.*;
19 20 21 22 23 24 25 26 27 28 29 30 31
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.handler.PresenceUpdateHandler;
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
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.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
32 33 34
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
35
import org.jivesoftware.util.lock.LockManager;
36 37
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
38 39 40 41
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;

42
import java.sql.Connection;
43
import java.sql.*;
44 45 46 47
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
48
import java.util.concurrent.locks.Lock;
49 50 51 52 53 54 55 56

/**
 * Simple in memory implementation of the PresenceManager interface.
 *
 * @author Iain Shigeoka
 */
public class PresenceManagerImpl extends BasicModule implements PresenceManager {

57 58 59 60 61 62 63 64 65
    private static final String LOAD_OFFLINE_PRESENCE =
            "SELECT offlinePresence, offlineDate FROM jivePresence WHERE username=?";
    private static final String INSERT_OFFLINE_PRESENCE =
            "INSERT INTO jivePresence(username, offlinePresence, offlineDate) VALUES(?,?,?)";
    private static final String DELETE_OFFLINE_PRESENCE =
            "DELETE FROM jivePresence WHERE username=?";

    private static final String NULL_STRING = "NULL";
    private static final long NULL_LONG = -1L;
66

67
    private RoutingTable routingTable;
68
    private SessionManager sessionManager;
69
    private UserManager userManager;
70
    private RosterManager rosterManager;
71 72 73 74 75 76
    private XMPPServer server;
    private PacketDeliverer deliverer;
    private PresenceUpdateHandler presenceUpdateHandler;

    private InternalComponentManager componentManager;

77 78
    private Cache<String, Long> lastActivityCache;
    private Cache<String, String> offlinePresenceCache;
79

80 81 82 83 84
    public PresenceManagerImpl() {
        super("Presence manager");
    }

    public boolean isAvailable(User user) {
85
        return sessionManager.getActiveSessionCount(user.getUsername()) > 0;
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    }

    public Presence getPresence(User user) {
        if (user == null) {
            return null;
        }
        Presence presence = null;

        for (ClientSession session : sessionManager.getSessions(user.getUsername())) {
            if (presence == null) {
                presence = session.getPresence();
            }
            else {
                // Get the ordinals of the presences to compare. If no ordinal is available then
                // assume a value of -1
                int o1 = presence.getShow() != null ? presence.getShow().ordinal() : -1;
                int o2 = session.getPresence().getShow() != null ?
                        session.getPresence().getShow().ordinal() : -1;
                // Compare the presences' show ordinals
                if (o1 > o2) {
                    presence = session.getPresence();
                }
            }
        }
        return presence;
    }

    public Collection<Presence> getPresences(String username) {
        if (username == null) {
            return null;
        }
        List<Presence> presences = new ArrayList<Presence>();

        for (ClientSession session : sessionManager.getSessions(username)) {
            presences.add(session.getPresence());
        }
        return Collections.unmodifiableCollection(presences);
    }

    public String getLastPresenceStatus(User user) {
126 127 128 129 130 131 132
        String username = user.getUsername();
        String presenceStatus = null;
        String presenceXML = offlinePresenceCache.get(username);
        if (presenceXML == null) {
            loadOfflinePresence(username);
        }
        presenceXML = offlinePresenceCache.get(username);
133
        if (presenceXML != null) {
134 135 136 137 138
            // If the cached answer is no data, return null.
            if (presenceXML.equals(NULL_STRING)) {
                return null;
            }
            // Otherwise, parse out the status from the XML.
139 140 141
            try {
                // Parse the element
                Document element = DocumentHelper.parseText(presenceXML);
142
                presenceStatus = element.getRootElement().elementTextTrim("status");
143 144 145 146 147
            }
            catch (DocumentException e) {
                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            }
        }
148
        return presenceStatus;
149 150 151
    }

    public long getLastActivity(User user) {
152 153 154 155 156 157 158 159 160 161 162
        String username = user.getUsername();
        long lastActivity = NULL_LONG;
        Long offlineDate = lastActivityCache.get(username);
        if (offlineDate == null) {
            loadOfflinePresence(username);
        }
        offlineDate = lastActivityCache.get(username);
        if (offlineDate != null) {
            // If the cached answer is no data, return -1.
            if (offlineDate == NULL_LONG) {
                return NULL_LONG;
163
            }
164 165 166 167 168 169 170
            else {
                try {
                    lastActivity = (System.currentTimeMillis() - offlineDate);
                }
                catch (NumberFormatException e) {
                    Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
                }
171 172
            }
        }
173
        return lastActivity;
174 175 176 177 178 179 180 181
    }

    public void userAvailable(Presence presence) {
        // Delete the last unavailable presence of this user since the user is now
        // available. Only perform this operation if this is an available presence sent to
        // THE SERVER and the presence belongs to a local user.
        if (presence.getTo() == null && server.isLocal(presence.getFrom())) {
            String username = presence.getFrom().getNode();
182
            if (username == null || !userManager.isRegisteredUser(username)) {
183 184 185
                // Ignore anonymous users
                return;
            }
186 187 188 189 190 191 192 193 194

            // Optimization: only delete the unavailable presence information if this
            // is the first session created on the server.
            if (sessionManager.getSessionCount(username) > 1) {
                return;
            }

            Connection con = null;
            PreparedStatement pstmt = null;
195
            try {
196 197 198 199
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(DELETE_OFFLINE_PRESENCE);
                pstmt.setString(1, username);
                pstmt.execute();
200
            }
201 202 203 204 205
            catch (SQLException sqle) {
                Log.error(sqle);
            }
            finally {
                DbConnectionManager.closeConnection(pstmt, con);
206
            }
Matt Tucker's avatar
Matt Tucker committed
207 208 209 210

            // Remove data from cache.
            offlinePresenceCache.remove(username);
            lastActivityCache.remove(username);
211 212 213 214 215 216 217 218 219
        }
    }

    public void userUnavailable(Presence presence) {
        // Only save the last presence status and keep track of the time when the user went
        // offline if this is an unavailable presence sent to THE SERVER and the presence belongs
        // to a local user.
        if (presence.getTo() == null && server.isLocal(presence.getFrom())) {
            String username = presence.getFrom().getNode();
220
            if (username == null || !userManager.isRegisteredUser(username)) {
221 222 223
                // Ignore anonymous users
                return;
            }
224 225

            // If the user has any remaining sessions, don't record the offline info.
226 227 228
            if (sessionManager.getActiveSessionCount(username) > 0) {
                return;
            }
229 230 231 232 233 234 235 236 237 238

            String offlinePresence = null;
            // Save the last unavailable presence of this user if the presence contains any
            // child element such as <status>.
            if (!presence.getElement().elements().isEmpty()) {
                offlinePresence = presence.toXML();
            }
            // Keep track of the time when the user went offline
            java.util.Date offlinePresenceDate = new java.util.Date();

239 240 241 242 243 244 245 246 247 248 249 250
            boolean addedToCache;
            if (offlinePresence == null) {
                addedToCache = !NULL_STRING.equals(offlinePresenceCache.put(username, NULL_STRING));
            }
            else {
                addedToCache = !offlinePresence.equals(offlinePresenceCache.put(username, offlinePresence));
            }
            if (!addedToCache) {
                return;
            }
            lastActivityCache.put(username, offlinePresenceDate.getTime());

251 252 253
            // Insert data into the database.
            Connection con = null;
            PreparedStatement pstmt = null;
254
            try {
255 256 257 258 259
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(INSERT_OFFLINE_PRESENCE);
                pstmt.setString(1, username);
                if (offlinePresence != null) {
                    DbConnectionManager.setLargeTextField(pstmt, 2, offlinePresence);
260
                }
261 262 263 264 265 266 267
                else {
                    pstmt.setNull(2, Types.VARCHAR);
                }
                pstmt.setString(3, StringUtils.dateToMillis(offlinePresenceDate));
                pstmt.execute();
            }
            catch (SQLException sqle) {
268
                Log.error("Error storing offline presence of user: " + username, sqle);
269
            }
270 271
            finally {
                DbConnectionManager.closeConnection(pstmt, con);
272 273 274 275 276 277 278
            }
        }
    }

    public void handleProbe(Presence packet) throws UnauthorizedException {
        String username = packet.getTo().getNode();
        try {
279
            Roster roster = rosterManager.getRoster(username);
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
            RosterItem item = roster.getRosterItem(packet.getFrom());
            if (item.getSubStatus() == RosterItem.SUB_FROM
                    || item.getSubStatus() == RosterItem.SUB_BOTH) {
                probePresence(packet.getFrom(),  packet.getTo());
            }
            else {
                PacketError.Condition error = PacketError.Condition.not_authorized;
                if ((item.getSubStatus() == RosterItem.SUB_NONE &&
                        item.getRecvStatus() != RosterItem.RECV_SUBSCRIBE) ||
                        (item.getSubStatus() == RosterItem.SUB_TO &&
                        item.getRecvStatus() != RosterItem.RECV_SUBSCRIBE)) {
                    error = PacketError.Condition.forbidden;
                }
                Presence presenceToSend = new Presence();
                presenceToSend.setError(error);
                presenceToSend.setTo(packet.getFrom());
                presenceToSend.setFrom(packet.getTo());
                deliverer.deliver(presenceToSend);
            }
        }
        catch (UserNotFoundException e) {
            Presence presenceToSend = new Presence();
            presenceToSend.setError(PacketError.Condition.forbidden);
            presenceToSend.setTo(packet.getFrom());
            presenceToSend.setFrom(packet.getTo());
            deliverer.deliver(presenceToSend);
        }
    }

    public boolean canProbePresence(JID prober, String probee) throws UserNotFoundException {
310
        RosterItem item = rosterManager.getRoster(probee).getRosterItem(prober);
311 312
        return item.getSubStatus() == RosterItem.SUB_FROM
                || item.getSubStatus() == RosterItem.SUB_BOTH;
313 314 315 316 317
    }

    public void probePresence(JID prober, JID probee) {
        try {
            if (server.isLocal(probee)) {
318 319 320 321 322 323 324 325 326 327
                // Local probers should receive presences of probee in all connected resources
                Collection<JID> proberFullJIDs = new ArrayList<JID>();
                if (prober.getResource() == null && server.isLocal(prober)) {
                    for (ClientSession session : sessionManager.getSessions(prober.getNode())) {
                        proberFullJIDs.add(session.getAddress());
                    }
                }
                else {
                    proberFullJIDs.add(prober);
                }
328 329
                // If the probee is a local user then don't send a probe to the contact's server.
                // But instead just send the contact's presence to the prober
330 331 332 333 334
                Collection<ClientSession> sessions = sessionManager.getSessions(probee.getNode());
                if (sessions.isEmpty()) {
                    // If the probee is not online then try to retrieve his last unavailable
                    // presence which may contain particular information and send it to the
                    // prober
335 336 337 338 339
                    String presenceXML = offlinePresenceCache.get(probee.getNode());
                    if (presenceXML == null) {
                        loadOfflinePresence(probee.getNode());
                    }
                    presenceXML = offlinePresenceCache.get(probee.getNode());
340
                    if (presenceXML != null && !NULL_STRING.equals(presenceXML)) {
341
                        try {
342 343 344 345 346 347 348 349 350
                            // Parse the element
                            Document element = DocumentHelper.parseText(presenceXML);
                            // Create the presence from the parsed element
                            Presence presencePacket = new Presence(element.getRootElement());
                            presencePacket.setFrom(probee.toBareJID());
                            // Check if default privacy list of the probee blocks the
                            // outgoing presence
                            PrivacyList list = PrivacyListManager.getInstance()
                                    .getDefaultPrivacyList(probee.getNode());
351 352 353 354 355 356 357
                            // Send presence to all prober's resources
                            for (JID receipient : proberFullJIDs) {
                                presencePacket.setTo(receipient);
                                if (list == null || !list.shouldBlockPacket(presencePacket)) {
                                    // Send the presence to the prober
                                    deliverer.deliver(presencePacket);
                                }
358 359
                            }
                        }
360 361
                        catch (Exception e) {
                            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
362 363
                        }
                    }
364 365 366 367 368 369 370 371 372 373 374
                }
                else {
                    // The contact is online so send to the prober all the resources where the
                    // probee is connected
                    for (ClientSession session : sessions) {
                        // Create presence to send from probee to prober
                        Presence presencePacket = session.getPresence().createCopy();
                        presencePacket.setFrom(session.getAddress());
                        // Check if a privacy list of the probee blocks the outgoing presence
                        PrivacyList list = session.getActiveList();
                        list = list == null ? session.getDefaultList() : list;
375 376 377 378 379 380 381 382 383 384 385 386 387 388
                        // Send presence to all prober's resources
                        for (JID receipient : proberFullJIDs) {
                            presencePacket.setTo(receipient);
                            if (list != null) {
                                if (list.shouldBlockPacket(presencePacket)) {
                                    // Default list blocked outgoing presence so skip this session
                                    continue;
                                }
                            }
                            try {
                                deliverer.deliver(presencePacket);
                            }
                            catch (Exception e) {
                                Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
389
                            }
390
                        }
391 392 393 394
                    }
                }
            }
            else {
395
                if (routingTable.hasComponentRoute(probee)) {
396 397 398 399 400 401
                    // If the probee belongs to a component then ask the component to process the
                    // probe presence
                    Presence presence = new Presence();
                    presence.setType(Presence.Type.probe);
                    presence.setFrom(prober);
                    presence.setTo(probee);
Gaston Dombiak's avatar
Gaston Dombiak committed
402
                    routingTable.routePacket(probee, presence, true);
403 404
                }
                else {
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
                    // Check if the probee may be hosted by this server
                    /*String serverDomain = server.getServerInfo().getName();
                    if (!probee.getDomain().contains(serverDomain)) {*/
                    if (server.isRemote(probee)) {
                        // Send the probe presence to the remote server
                        Presence probePresence = new Presence();
                        probePresence.setType(Presence.Type.probe);
                        probePresence.setFrom(prober);
                        probePresence.setTo(probee.toBareJID());
                        // Send the probe presence
                        deliverer.deliver(probePresence);
                    }
                    else {
                        // The probee may be related to a component that has not yet been connected so
                        // we will keep a registry of this presence probe. The component will answer
                        // this presence probe when he becomes online
                        componentManager.addPresenceRequest(prober, probee);
                    }
423 424 425 426 427 428 429 430 431
                }
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
        }
    }

    public void sendUnavailableFromSessions(JID recipientJID, JID userJID) {
432
        if (userManager.isRegisteredUser(userJID.getNode())) {
433 434 435 436 437 438 439 440
            for (ClientSession session : sessionManager.getSessions(userJID.getNode())) {
                // Do not send an unavailable presence if the user sent a direct available presence
                if (presenceUpdateHandler.hasDirectPresence(session, recipientJID)) {
                    continue;
                }
                Presence presencePacket = new Presence();
                presencePacket.setType(Presence.Type.unavailable);
                presencePacket.setFrom(session.getAddress());
441 442 443 444 445 446 447
                // Ensure that unavailable presence is sent to all receipient's resources
                Collection<JID> recipientFullJIDs = new ArrayList<JID>();
                if (server.isLocal(recipientJID)) {
                    for (ClientSession targetSession : sessionManager
                            .getSessions(recipientJID.getNode())) {
                        recipientFullJIDs.add(targetSession.getAddress());
                    }
448
                }
449 450 451 452 453 454 455 456 457 458 459
                else {
                    recipientFullJIDs.add(recipientJID);
                }
                for (JID jid : recipientFullJIDs) {
                    presencePacket.setTo(jid);
                    try {
                        deliverer.deliver(presencePacket);
                    }
                    catch (Exception e) {
                        Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
                    }
460 461 462 463 464 465 466 467 468 469 470 471
                }
            }
        }
    }

    // #####################################################################
    // Module management
    // #####################################################################

    public void initialize(XMPPServer server) {
        super.initialize(server);
        this.server = server;
472

473 474
        offlinePresenceCache = CacheFactory.createCache("Offline Presence Cache");
        lastActivityCache = CacheFactory.createCache("Last Activity Cache");
475

476 477
        deliverer = server.getPacketDeliverer();
        sessionManager = server.getSessionManager();
478
        userManager = server.getUserManager();
479
        presenceUpdateHandler = server.getPresenceUpdateHandler();
480
        rosterManager = server.getRosterManager();
481
        routingTable = server.getRoutingTable();
482 483
    }

484 485 486 487 488 489 490
    public void start() throws IllegalStateException {
        super.start();
        // Use component manager for Presence Updates.
        componentManager = InternalComponentManager.getInstance();

    }

491 492 493 494 495 496
    public void stop() {
        // Clear the caches when stopping the module.
        offlinePresenceCache.clear();
        lastActivityCache.clear();
    }

497 498 499 500 501 502 503 504 505
    /**
     * Loads offline presence data for the user into cache.
     *
     * @param username the username.
     */
    private void loadOfflinePresence(String username) {
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
506
        Lock lock = LockManager.getLock(username + "pr");
507
        try {
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
            lock.lock();
            if (!offlinePresenceCache.containsKey(username) || !lastActivityCache.containsKey(username)) {
                con = DbConnectionManager.getConnection();
                pstmt = con.prepareStatement(LOAD_OFFLINE_PRESENCE);
                pstmt.setString(1, username);
                rs = pstmt.executeQuery();
                if (rs.next()) {
                    String offlinePresence = DbConnectionManager.getLargeTextField(rs, 1);
                    if (rs.wasNull()) {
                        offlinePresence = NULL_STRING;
                    }
                    long offlineDate = Long.parseLong(rs.getString(2).trim());
                    offlinePresenceCache.put(username, offlinePresence);
                    lastActivityCache.put(username, offlineDate);
                }
                else {
                    offlinePresenceCache.put(username, NULL_STRING);
                    lastActivityCache.put(username, NULL_LONG);
526 527 528 529 530 531 532 533
                }
            }
        }
        catch (SQLException sqle) {
            Log.error(sqle);
        }
        finally {
            DbConnectionManager.closeConnection(rs, pstmt, con);
534
            lock.unlock();
535 536
        }
    }
537
}