Roster.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smack.roster;

  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashSet;
  23. import java.util.LinkedHashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Map.Entry;
  27. import java.util.Set;
  28. import java.util.WeakHashMap;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.CopyOnWriteArraySet;
  31. import java.util.logging.Level;
  32. import java.util.logging.Logger;

  33. import org.jivesoftware.smack.AbstractConnectionClosedListener;
  34. import org.jivesoftware.smack.ConnectionCreationListener;
  35. import org.jivesoftware.smack.ExceptionCallback;
  36. import org.jivesoftware.smack.Manager;
  37. import org.jivesoftware.smack.StanzaListener;
  38. import org.jivesoftware.smack.SmackException;
  39. import org.jivesoftware.smack.XMPPConnection;
  40. import org.jivesoftware.smack.SmackException.NoResponseException;
  41. import org.jivesoftware.smack.SmackException.NotConnectedException;
  42. import org.jivesoftware.smack.SmackException.NotLoggedInException;
  43. import org.jivesoftware.smack.XMPPConnectionRegistry;
  44. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  45. import org.jivesoftware.smack.filter.StanzaFilter;
  46. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  47. import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
  48. import org.jivesoftware.smack.packet.IQ;
  49. import org.jivesoftware.smack.packet.IQ.Type;
  50. import org.jivesoftware.smack.packet.Stanza;
  51. import org.jivesoftware.smack.packet.Presence;
  52. import org.jivesoftware.smack.packet.XMPPError;
  53. import org.jivesoftware.smack.packet.XMPPError.Condition;
  54. import org.jivesoftware.smack.roster.packet.RosterPacket;
  55. import org.jivesoftware.smack.roster.packet.RosterVer;
  56. import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
  57. import org.jivesoftware.smack.roster.rosterstore.RosterStore;
  58. import org.jivesoftware.smack.util.Objects;
  59. import org.jxmpp.jid.BareJid;
  60. import org.jxmpp.jid.Jid;
  61. import org.jxmpp.jid.JidWithResource;
  62. import org.jxmpp.jid.impl.JidCreate;
  63. import org.jxmpp.jid.parts.Resourcepart;

  64. /**
  65.  * Represents a user's roster, which is the collection of users a person receives
  66.  * presence updates for. Roster items are categorized into groups for easier management.<p>
  67.  * <p/>
  68.  * Others users may attempt to subscribe to this user using a subscription request. Three
  69.  * modes are supported for handling these requests: <ul>
  70.  * <li>{@link SubscriptionMode#accept_all accept_all} -- accept all subscription requests.</li>
  71.  * <li>{@link SubscriptionMode#reject_all reject_all} -- reject all subscription requests.</li>
  72.  * <li>{@link SubscriptionMode#manual manual} -- manually process all subscription requests.</li>
  73.  * </ul>
  74.  *
  75.  * @author Matt Tucker
  76.  * @see #getInstanceFor(XMPPConnection)
  77.  */
  78. public class Roster extends Manager {

  79.     private static final Logger LOGGER = Logger.getLogger(Roster.class.getName());

  80.     static {
  81.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  82.             @Override
  83.             public void connectionCreated(XMPPConnection connection) {
  84.                 getInstanceFor(connection);
  85.             }
  86.         });
  87.     }

  88.     private static final Map<XMPPConnection, Roster> INSTANCES = new WeakHashMap<>();

  89.     /**
  90.      * Returns the roster for the user.
  91.      * <p>
  92.      * This method will never return <code>null</code>, instead if the user has not yet logged into
  93.      * the server or is logged in anonymously all modifying methods of the returned roster object
  94.      * like {@link Roster#createEntry(Jid, String, String[])},
  95.      * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing
  96.      * {@link RosterListener}s will throw an IllegalStateException.
  97.      *
  98.      * @return the user's roster.
  99.      * @throws IllegalStateException if the connection is anonymous
  100.      */
  101.     public static synchronized Roster getInstanceFor(XMPPConnection connection) {
  102.         Roster roster = INSTANCES.get(connection);
  103.         if (roster == null) {
  104.             roster = new Roster(connection);
  105.             INSTANCES.put(connection, roster);
  106.         }
  107.         return roster;
  108.     }

  109.     private static final StanzaFilter PRESENCE_PACKET_FILTER = StanzaTypeFilter.PRESENCE;

  110.     private static boolean rosterLoadedAtLoginDefault = true;

  111.     /**
  112.      * The default subscription processing mode to use when a Roster is created. By default
  113.      * all subscription requests are automatically accepted.
  114.      */
  115.     private static SubscriptionMode defaultSubscriptionMode = SubscriptionMode.accept_all;

  116.     private RosterStore rosterStore;
  117.     private final Map<String, RosterGroup> groups = new ConcurrentHashMap<String, RosterGroup>();

  118.     /**
  119.      * Concurrent hash map from JID to its roster entry.
  120.      */
  121.     private final Map<Jid, RosterEntry> entries = new ConcurrentHashMap<>();

  122.     private final Set<RosterEntry> unfiledEntries = new CopyOnWriteArraySet<>();
  123.     private final Set<RosterListener> rosterListeners = new LinkedHashSet<>();

  124.     /**
  125.      * A map of JIDs to another Map of Resourceparts to Presences. The 'inner' map may contain
  126.      * {@link Resourcepart#EMPTY} if there are no other Presences available.
  127.      */
  128.     private final Map<Jid, Map<Resourcepart, Presence>> presenceMap = new ConcurrentHashMap<>();

  129.     /**
  130.      * Listeners called when the Roster was loaded.
  131.      */
  132.     private final Set<RosterLoadedListener> rosterLoadedListeners = new LinkedHashSet<>();

  133.     /**
  134.      * Mutually exclude roster listener invocation and changing the {@link entries} map. Also used
  135.      * to synchronize access to either the roster listeners or the entries map.
  136.      */
  137.     private final Object rosterListenersAndEntriesLock = new Object();

  138.     // The roster is marked as initialized when at least a single roster packet
  139.     // has been received and processed.
  140.     private boolean loaded = false;

  141.     private final PresencePacketListener presencePacketListener = new PresencePacketListener();

  142.     /**
  143.      *
  144.      */
  145.     private boolean rosterLoadedAtLogin = rosterLoadedAtLoginDefault;

  146.     private SubscriptionMode subscriptionMode = getDefaultSubscriptionMode();

  147.     /**
  148.      * Returns the default subscription processing mode to use when a new Roster is created. The
  149.      * subscription processing mode dictates what action Smack will take when subscription
  150.      * requests from other users are made. The default subscription mode
  151.      * is {@link SubscriptionMode#accept_all}.
  152.      *
  153.      * @return the default subscription mode to use for new Rosters
  154.      */
  155.     public static SubscriptionMode getDefaultSubscriptionMode() {
  156.         return defaultSubscriptionMode;
  157.     }

  158.     /**
  159.      * Sets the default subscription processing mode to use when a new Roster is created. The
  160.      * subscription processing mode dictates what action Smack will take when subscription
  161.      * requests from other users are made. The default subscription mode
  162.      * is {@link SubscriptionMode#accept_all}.
  163.      *
  164.      * @param subscriptionMode the default subscription mode to use for new Rosters.
  165.      */
  166.     public static void setDefaultSubscriptionMode(SubscriptionMode subscriptionMode) {
  167.         defaultSubscriptionMode = subscriptionMode;
  168.     }

  169.     /**
  170.      * Creates a new roster.
  171.      *
  172.      * @param connection an XMPP connection.
  173.      */
  174.     private Roster(final XMPPConnection connection) {
  175.         super(connection);

  176.         // Note that we use sync packet listeners because RosterListeners should be invoked in the same order as the
  177.         // roster stanzas arrive.
  178.         // Listen for any roster packets.
  179.         connection.registerIQRequestHandler(new RosterPushListener());
  180.         // Listen for any presence packets.
  181.         connection.addSyncStanzaListener(presencePacketListener, PRESENCE_PACKET_FILTER);

  182.         // Listen for connection events
  183.         connection.addConnectionListener(new AbstractConnectionClosedListener() {

  184.             @Override
  185.             public void authenticated(XMPPConnection connection, boolean resumed) {
  186.                 // Anonymous users can't have a roster, but it is possible that a Roster instance is
  187.                 // retrieved if getRoster() is called *before* connect(). So we have to check here
  188.                 // again if it's an anonymous connection.
  189.                 if (connection.isAnonymous())
  190.                     return;
  191.                 if (!isRosterLoadedAtLogin())
  192.                     return;
  193.                 // We are done here if the connection was resumed
  194.                 if (resumed) {
  195.                     return;
  196.                 }
  197.                 try {
  198.                     Roster.this.reload();
  199.                 }
  200.                 catch (InterruptedException | SmackException e) {
  201.                     LOGGER.log(Level.SEVERE, "Could not reload Roster", e);
  202.                     return;
  203.                 }
  204.             }

  205.             @Override
  206.             public void connectionTerminated() {
  207.                 // Changes the presence available contacts to unavailable
  208.                 setOfflinePresencesAndResetLoaded();
  209.             }

  210.         });
  211.         // If the connection is already established, call reload
  212.         if (connection.isAuthenticated()) {
  213.             try {
  214.                 reload();
  215.             }
  216.             catch (InterruptedException | SmackException e) {
  217.                 LOGGER.log(Level.SEVERE, "Could not reload Roster", e);
  218.             }
  219.         }
  220.     }

  221.     /**
  222.      * Returns the subscription processing mode, which dictates what action
  223.      * Smack will take when subscription requests from other users are made.
  224.      * The default subscription mode is {@link SubscriptionMode#accept_all}.<p>
  225.      * <p/>
  226.      * If using the manual mode, a PacketListener should be registered that
  227.      * listens for Presence packets that have a type of
  228.      * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
  229.      *
  230.      * @return the subscription mode.
  231.      */
  232.     public SubscriptionMode getSubscriptionMode() {
  233.         return subscriptionMode;
  234.     }

  235.     /**
  236.      * Sets the subscription processing mode, which dictates what action
  237.      * Smack will take when subscription requests from other users are made.
  238.      * The default subscription mode is {@link SubscriptionMode#accept_all}.<p>
  239.      * <p/>
  240.      * If using the manual mode, a PacketListener should be registered that
  241.      * listens for Presence packets that have a type of
  242.      * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
  243.      *
  244.      * @param subscriptionMode the subscription mode.
  245.      */
  246.     public void setSubscriptionMode(SubscriptionMode subscriptionMode) {
  247.         this.subscriptionMode = subscriptionMode;
  248.     }

  249.     /**
  250.      * Reloads the entire roster from the server. This is an asynchronous operation,
  251.      * which means the method will return immediately, and the roster will be
  252.      * reloaded at a later point when the server responds to the reload request.
  253.      * @throws NotLoggedInException If not logged in.
  254.      * @throws NotConnectedException
  255.      * @throws InterruptedException
  256.      */
  257.     public void reload() throws NotLoggedInException, NotConnectedException, InterruptedException{
  258.         final XMPPConnection connection = connection();
  259.         if (!connection.isAuthenticated()) {
  260.             throw new NotLoggedInException();
  261.         }
  262.         if (connection.isAnonymous()) {
  263.             throw new IllegalStateException("Anonymous users can't have a roster.");
  264.         }

  265.         RosterPacket packet = new RosterPacket();
  266.         if (rosterStore != null && isRosterVersioningSupported()) {
  267.             packet.setVersion(rosterStore.getRosterVersion());
  268.         }
  269.         connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
  270.             @Override
  271.             public void processException(Exception exception) {
  272.                 LOGGER.log(Level.SEVERE, "Exception reloading roster" , exception);
  273.             }
  274.         });
  275.     }

  276.     /**
  277.      * Reload the roster and block until it is reloaded.
  278.      *
  279.      * @throws NotLoggedInException
  280.      * @throws NotConnectedException
  281.      * @throws InterruptedException
  282.      * @since 4.1
  283.      */
  284.     public void reloadAndWait() throws NotLoggedInException, NotConnectedException, InterruptedException {
  285.         reload();
  286.         waitUntilLoaded();
  287.     }
  288.  
  289.     /**
  290.      * Set the roster store, may cause a roster reload
  291.      *
  292.      * @param rosterStore
  293.      * @return true if the roster reload was initiated, false otherwise.
  294.      * @since 4.1
  295.      */
  296.     public boolean setRosterStore(RosterStore rosterStore) {
  297.         this.rosterStore = rosterStore;
  298.         try {
  299.             reload();
  300.         }
  301.         catch (InterruptedException | NotLoggedInException | NotConnectedException e) {
  302.             LOGGER.log(Level.FINER, "Could not reload roster", e);
  303.             return false;
  304.         }
  305.         return true;
  306.     }

  307.     protected boolean waitUntilLoaded() throws InterruptedException {
  308.         final XMPPConnection connection = connection();
  309.         while (!loaded) {
  310.             long waitTime = connection.getPacketReplyTimeout();
  311.             long start = System.currentTimeMillis();
  312.             if (waitTime <= 0) {
  313.                 break;
  314.             }
  315.             synchronized (this) {
  316.                 if (!loaded) {
  317.                     wait(waitTime);
  318.                 }
  319.             }
  320.             long now = System.currentTimeMillis();
  321.             waitTime -= now - start;
  322.             start = now;
  323.         }
  324.         return isLoaded();
  325.     }

  326.     /**
  327.      * Check if the roster is loaded.
  328.      *
  329.      * @return true if the roster is loaded.
  330.      * @since 4.1
  331.      */
  332.     public boolean isLoaded() {
  333.         return loaded;
  334.     }

  335.     /**
  336.      * Adds a listener to this roster. The listener will be fired anytime one or more
  337.      * changes to the roster are pushed from the server.
  338.      *
  339.      * @param rosterListener a roster listener.
  340.      * @return true if the listener was not already added.
  341.      * @see #getEntriesAndAddListener(RosterListener, RosterEntries)
  342.      */
  343.     public boolean addRosterListener(RosterListener rosterListener) {
  344.         synchronized (rosterListenersAndEntriesLock) {
  345.             return rosterListeners.add(rosterListener);
  346.         }
  347.     }

  348.     /**
  349.      * Removes a listener from this roster. The listener will be fired anytime one or more
  350.      * changes to the roster are pushed from the server.
  351.      *
  352.      * @param rosterListener a roster listener.
  353.      * @return true if the listener was active and got removed.
  354.      */
  355.     public boolean removeRosterListener(RosterListener rosterListener) {
  356.         synchronized (rosterListenersAndEntriesLock) {
  357.             return rosterListeners.remove(rosterListener);
  358.         }
  359.     }

  360.     /**
  361.      * Add a roster loaded listener.
  362.      *
  363.      * @param rosterLoadedListener the listener to add.
  364.      * @return true if the listener was not already added.
  365.      * @see RosterLoadedListener
  366.      * @since 4.1
  367.      */
  368.     public boolean addRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
  369.         synchronized (rosterLoadedListener) {
  370.             return rosterLoadedListeners.add(rosterLoadedListener);
  371.         }
  372.     }

  373.     /**
  374.      * Remove a roster loaded listener.
  375.      *
  376.      * @param rosterLoadedListener the listener to remove.
  377.      * @return true if the listener was active and got removed.
  378.      * @see RosterLoadedListener
  379.      * @since 4.1
  380.      */
  381.     public boolean removeRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
  382.         synchronized (rosterLoadedListener) {
  383.             return rosterLoadedListeners.remove(rosterLoadedListener);
  384.         }
  385.     }

  386.     /**
  387.      * Creates a new group.<p>
  388.      * <p/>
  389.      * Note: you must add at least one entry to the group for the group to be kept
  390.      * after a logout/login. This is due to the way that XMPP stores group information.
  391.      *
  392.      * @param name the name of the group.
  393.      * @return a new group, or null if the group already exists
  394.      * @throws IllegalStateException if logged in anonymously
  395.      */
  396.     public RosterGroup createGroup(String name) {
  397.         final XMPPConnection connection = connection();
  398.         if (connection.isAnonymous()) {
  399.             throw new IllegalStateException("Anonymous users can't have a roster.");
  400.         }
  401.         if (groups.containsKey(name)) {
  402.             return groups.get(name);
  403.         }
  404.        
  405.         RosterGroup group = new RosterGroup(name, connection);
  406.         groups.put(name, group);
  407.         return group;
  408.     }

  409.     /**
  410.      * Creates a new roster entry and presence subscription. The server will asynchronously
  411.      * update the roster with the subscription status.
  412.      *
  413.      * @param user   the user. (e.g. johndoe@jabber.org)
  414.      * @param name   the nickname of the user.
  415.      * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
  416.      *               the roster entry won't belong to a group.
  417.      * @throws NoResponseException if there was no response from the server.
  418.      * @throws XMPPErrorException if an XMPP exception occurs.
  419.      * @throws NotLoggedInException If not logged in.
  420.      * @throws NotConnectedException
  421.      * @throws InterruptedException
  422.      */
  423.     public void createEntry(Jid user, String name, String[] groups) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  424.         final XMPPConnection connection = connection();
  425.         if (!connection.isAuthenticated()) {
  426.             throw new NotLoggedInException();
  427.         }
  428.         if (connection.isAnonymous()) {
  429.             throw new IllegalStateException("Anonymous users can't have a roster.");
  430.         }

  431.         // Create and send roster entry creation packet.
  432.         RosterPacket rosterPacket = new RosterPacket();
  433.         rosterPacket.setType(IQ.Type.set);
  434.         RosterPacket.Item item = new RosterPacket.Item(user, name);
  435.         if (groups != null) {
  436.             for (String group : groups) {
  437.                 if (group != null && group.trim().length() > 0) {
  438.                     item.addGroupName(group);
  439.                 }
  440.             }
  441.         }
  442.         rosterPacket.addRosterItem(item);
  443.         connection.createPacketCollectorAndSend(rosterPacket).nextResultOrThrow();

  444.         // Create a presence subscription packet and send.
  445.         Presence presencePacket = new Presence(Presence.Type.subscribe);
  446.         presencePacket.setTo(user);
  447.         connection.sendStanza(presencePacket);
  448.     }

  449.     /**
  450.      * Removes a roster entry from the roster. The roster entry will also be removed from the
  451.      * unfiled entries or from any roster group where it could belong and will no longer be part
  452.      * of the roster. Note that this is a synchronous call -- Smack must wait for the server
  453.      * to send an updated subscription status.
  454.      *
  455.      * @param entry a roster entry.
  456.      * @throws XMPPErrorException if an XMPP error occurs.
  457.      * @throws NotLoggedInException if not logged in.
  458.      * @throws NoResponseException SmackException if there was no response from the server.
  459.      * @throws NotConnectedException
  460.      * @throws InterruptedException
  461.      * @throws IllegalStateException if connection is not logged in or logged in anonymously
  462.      */
  463.     public void removeEntry(RosterEntry entry) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  464.         final XMPPConnection connection = connection();
  465.         if (!connection.isAuthenticated()) {
  466.             throw new NotLoggedInException();
  467.         }
  468.         if (connection.isAnonymous()) {
  469.             throw new IllegalStateException("Anonymous users can't have a roster.");
  470.         }

  471.         // Only remove the entry if it's in the entry list.
  472.         // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
  473.         if (!entries.containsKey(entry.getUser())) {
  474.             return;
  475.         }
  476.         RosterPacket packet = new RosterPacket();
  477.         packet.setType(IQ.Type.set);
  478.         RosterPacket.Item item = RosterEntry.toRosterItem(entry);
  479.         // Set the item type as REMOVE so that the server will delete the entry
  480.         item.setItemType(RosterPacket.ItemType.remove);
  481.         packet.addRosterItem(item);
  482.         connection.createPacketCollectorAndSend(packet).nextResultOrThrow();
  483.     }

  484.     /**
  485.      * Returns a count of the entries in the roster.
  486.      *
  487.      * @return the number of entries in the roster.
  488.      */
  489.     public int getEntryCount() {
  490.         return getEntries().size();
  491.     }

  492.     /**
  493.      * Add a roster listener and invoke the roster entries with all entries of the roster.
  494.      * <p>
  495.      * The method guarantees that the listener is only invoked after
  496.      * {@link RosterEntries#rosterEntires(Collection)} has been invoked, and that all roster events
  497.      * that happen while <code>rosterEntires(Collection) </code> is called are queued until the
  498.      * method returns.
  499.      * </p>
  500.      * <p>
  501.      * This guarantee makes this the ideal method to e.g. populate a UI element with the roster while
  502.      * installing a {@link RosterListener} to listen for subsequent roster events.
  503.      * </p>
  504.      *
  505.      * @param rosterListener the listener to install
  506.      * @param rosterEntries the roster entries callback interface
  507.      * @since 4.1
  508.      */
  509.     public void getEntriesAndAddListener(RosterListener rosterListener, RosterEntries rosterEntries) {
  510.         Objects.requireNonNull(rosterListener, "listener must not be null");
  511.         Objects.requireNonNull(rosterEntries, "rosterEntries must not be null");

  512.         synchronized (rosterListenersAndEntriesLock) {
  513.             rosterEntries.rosterEntires(entries.values());
  514.             addRosterListener(rosterListener);
  515.         }
  516.     }

  517.     /**
  518.      * Returns a set of all entries in the roster, including entries
  519.      * that don't belong to any groups.
  520.      *
  521.      * @return all entries in the roster.
  522.      */
  523.     public Set<RosterEntry> getEntries() {
  524.         Set<RosterEntry> allEntries;
  525.         synchronized (rosterListenersAndEntriesLock) {
  526.             allEntries = new HashSet<>(entries.size());
  527.             for (RosterEntry entry : entries.values()) {
  528.                 allEntries.add(entry);
  529.             }
  530.         }
  531.         return allEntries;
  532.     }

  533.     /**
  534.      * Returns a count of the unfiled entries in the roster. An unfiled entry is
  535.      * an entry that doesn't belong to any groups.
  536.      *
  537.      * @return the number of unfiled entries in the roster.
  538.      */
  539.     public int getUnfiledEntryCount() {
  540.         return unfiledEntries.size();
  541.     }

  542.     /**
  543.      * Returns an unmodifiable set for the unfiled roster entries. An unfiled entry is
  544.      * an entry that doesn't belong to any groups.
  545.      *
  546.      * @return the unfiled roster entries.
  547.      */
  548.     public Set<RosterEntry> getUnfiledEntries() {
  549.         return Collections.unmodifiableSet(unfiledEntries);
  550.     }

  551.     /**
  552.      * Returns the roster entry associated with the given XMPP address or
  553.      * <tt>null</tt> if the user is not an entry in the roster.
  554.      *
  555.      * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
  556.      *             in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
  557.      * @return the roster entry or <tt>null</tt> if it does not exist.
  558.      */
  559.     public RosterEntry getEntry(Jid user) {
  560.         if (user == null) {
  561.             return null;
  562.         }
  563.         Jid key = getMapKey(user);
  564.         return entries.get(key);
  565.     }

  566.     /**
  567.      * Returns true if the specified XMPP address is an entry in the roster.
  568.      *
  569.      * @param user the XMPP address of the user (eg "jsmith@example.com"). The
  570.      *             address could be in any valid format (e.g. "domain/resource",
  571.      *             "user@domain" or "user@domain/resource").
  572.      * @return true if the XMPP address is an entry in the roster.
  573.      */
  574.     public boolean contains(Jid user) {
  575.         return getEntry(user) != null;
  576.     }

  577.     /**
  578.      * Returns the roster group with the specified name, or <tt>null</tt> if the
  579.      * group doesn't exist.
  580.      *
  581.      * @param name the name of the group.
  582.      * @return the roster group with the specified name.
  583.      */
  584.     public RosterGroup getGroup(String name) {
  585.         return groups.get(name);
  586.     }

  587.     /**
  588.      * Returns the number of the groups in the roster.
  589.      *
  590.      * @return the number of groups in the roster.
  591.      */
  592.     public int getGroupCount() {
  593.         return groups.size();
  594.     }

  595.     /**
  596.      * Returns an unmodifiable collections of all the roster groups.
  597.      *
  598.      * @return an iterator for all roster groups.
  599.      */
  600.     public Collection<RosterGroup> getGroups() {
  601.         return Collections.unmodifiableCollection(groups.values());
  602.     }

  603.     /**
  604.      * Returns the presence info for a particular user. If the user is offline, or
  605.      * if no presence data is available (such as when you are not subscribed to the
  606.      * user's presence updates), unavailable presence will be returned.
  607.      * <p>
  608.      * If the user has several presences (one for each resource), then the presence with
  609.      * highest priority will be returned. If multiple presences have the same priority,
  610.      * the one with the "most available" presence mode will be returned. In order,
  611.      * that's {@link org.jivesoftware.smack.packet.Presence.Mode#chat free to chat},
  612.      * {@link org.jivesoftware.smack.packet.Presence.Mode#available available},
  613.      * {@link org.jivesoftware.smack.packet.Presence.Mode#away away},
  614.      * {@link org.jivesoftware.smack.packet.Presence.Mode#xa extended away}, and
  615.      * {@link org.jivesoftware.smack.packet.Presence.Mode#dnd do not disturb}.<p>
  616.      * </p>
  617.      * <p>
  618.      * Note that presence information is received asynchronously. So, just after logging
  619.      * in to the server, presence values for users in the roster may be unavailable
  620.      * even if they are actually online. In other words, the value returned by this
  621.      * method should only be treated as a snapshot in time, and may not accurately reflect
  622.      * other user's presence instant by instant. If you need to track presence over time,
  623.      * such as when showing a visual representation of the roster, consider using a
  624.      * {@link RosterListener}.
  625.      * </p>
  626.      *
  627.      * @param user an XMPP ID. The address could be in any valid format (e.g.
  628.      *             "domain/resource", "user@domain" or "user@domain/resource"). Any resource
  629.      *             information that's part of the ID will be discarded.
  630.      * @return the user's current presence, or unavailable presence if the user is offline
  631.      *         or if no presence information is available..
  632.      */
  633.     public Presence getPresence(Jid user) {
  634.         Jid key = getMapKey(user);
  635.         Map<Resourcepart, Presence> userPresences = presenceMap.get(key);
  636.         if (userPresences == null) {
  637.             Presence presence = new Presence(Presence.Type.unavailable);
  638.             presence.setFrom(user);
  639.             return presence;
  640.         }
  641.         else {
  642.             // Find the resource with the highest priority
  643.             // Might be changed to use the resource with the highest availability instead.
  644.             Presence presence = null;
  645.             // This is used in case no available presence is found
  646.             Presence unavailable = null;

  647.             for (Resourcepart resource : userPresences.keySet()) {
  648.                 Presence p = userPresences.get(resource);
  649.                 if (!p.isAvailable()) {
  650.                     unavailable = p;
  651.                     continue;
  652.                 }
  653.                 // Chose presence with highest priority first.
  654.                 if (presence == null || p.getPriority() > presence.getPriority()) {
  655.                     presence = p;
  656.                 }
  657.                 // If equal priority, choose "most available" by the mode value.
  658.                 else if (p.getPriority() == presence.getPriority()) {
  659.                     Presence.Mode pMode = p.getMode();
  660.                     // Default to presence mode of available.
  661.                     if (pMode == null) {
  662.                         pMode = Presence.Mode.available;
  663.                     }
  664.                     Presence.Mode presenceMode = presence.getMode();
  665.                     // Default to presence mode of available.
  666.                     if (presenceMode == null) {
  667.                         presenceMode = Presence.Mode.available;
  668.                     }
  669.                     if (pMode.compareTo(presenceMode) < 0) {
  670.                         presence = p;
  671.                     }
  672.                 }
  673.             }
  674.             if (presence == null) {
  675.                 if (unavailable != null) {
  676.                     return unavailable.clone();
  677.                 }
  678.                 else {
  679.                     presence = new Presence(Presence.Type.unavailable);
  680.                     presence.setFrom(user);
  681.                     return presence;
  682.                 }
  683.             }
  684.             else {
  685.                 return presence.clone();
  686.             }
  687.         }
  688.     }

  689.     /**
  690.      * Returns the presence info for a particular user's resource, or unavailable presence
  691.      * if the user is offline or if no presence information is available, such as
  692.      * when you are not subscribed to the user's presence updates.
  693.      *
  694.      * @param userWithResource a fully qualified XMPP ID including a resource (user@domain/resource).
  695.      * @return the user's current presence, or unavailable presence if the user is offline
  696.      *         or if no presence information is available.
  697.      */
  698.     public Presence getPresenceResource(JidWithResource userWithResource) {
  699.         Jid key = getMapKey(userWithResource);
  700.         Resourcepart resource = userWithResource.getResourcepart();
  701.         Map<Resourcepart, Presence> userPresences = presenceMap.get(key);
  702.         if (userPresences == null) {
  703.             Presence presence = new Presence(Presence.Type.unavailable);
  704.             presence.setFrom(userWithResource);
  705.             return presence;
  706.         }
  707.         else {
  708.             Presence presence = userPresences.get(resource);
  709.             if (presence == null) {
  710.                 presence = new Presence(Presence.Type.unavailable);
  711.                 presence.setFrom(userWithResource);
  712.                 return presence;
  713.             }
  714.             else {
  715.                 return presence.clone();
  716.             }
  717.         }
  718.     }

  719.     /**
  720.      * Returns a List of Presence objects for all of a user's current presences if no presence information is available,
  721.      * such as when you are not subscribed to the user's presence updates.
  722.      *
  723.      * @param bareJid an XMPP ID, e.g. jdoe@example.com.
  724.      * @return a List of Presence objects for all the user's current presences, or an unavailable presence if no
  725.      *         presence information is available.
  726.      */
  727.     public List<Presence> getAllPresences(Jid bareJid) {
  728.         Map<Resourcepart, Presence> userPresences = presenceMap.get(getMapKey(bareJid));
  729.         List<Presence> res;
  730.         if (userPresences == null) {
  731.             // Create an unavailable presence if none was found
  732.             Presence unavailable = new Presence(Presence.Type.unavailable);
  733.             unavailable.setFrom(bareJid);
  734.             res = new ArrayList<>(Arrays.asList(unavailable));
  735.         } else {
  736.             res = new ArrayList<>(userPresences.values().size());
  737.             for (Presence presence : userPresences.values()) {
  738.                 res.add(presence.clone());
  739.             }
  740.         }
  741.         return res;
  742.     }

  743.     /**
  744.      * Returns a List of all <b>available</b> Presence Objects for the given bare JID. If there are no available
  745.      * presences, then the empty list will be returned.
  746.      *
  747.      * @param bareJid the bare JID from which the presences should be retrieved.
  748.      * @return available presences for the bare JID.
  749.      */
  750.     public List<Presence> getAvailablePresences(Jid bareJid) {
  751.         List<Presence> allPresences = getAllPresences(bareJid);
  752.         List<Presence> res = new ArrayList<>(allPresences.size());
  753.         for (Presence presence : allPresences) {
  754.             if (presence.isAvailable()) {
  755.                 // No need to clone presence here, getAllPresences already returns clones
  756.                 res.add(presence);
  757.             }
  758.         }
  759.         return res;
  760.     }

  761.     /**
  762.      * Returns a List of Presence objects for all of a user's current presences
  763.      * or an unavailable presence if the user is unavailable (offline) or if no presence
  764.      * information is available, such as when you are not subscribed to the user's presence
  765.      * updates.
  766.      *
  767.      * @param user an XMPP ID, e.g. jdoe@example.com.
  768.      * @return a List of Presence objects for all the user's current presences,
  769.      *         or an unavailable presence if the user is offline or if no presence information
  770.      *         is available.
  771.      */
  772.     public List<Presence> getPresences(Jid user) {
  773.         List<Presence> res;
  774.         Jid key = getMapKey(user);
  775.         Map<Resourcepart, Presence> userPresences = presenceMap.get(key);
  776.         if (userPresences == null) {
  777.             Presence presence = new Presence(Presence.Type.unavailable);
  778.             presence.setFrom(user);
  779.             res = Arrays.asList(presence);
  780.         }
  781.         else {
  782.             List<Presence> answer = new ArrayList<Presence>();
  783.             // Used in case no available presence is found
  784.             Presence unavailable = null;
  785.             for (Presence presence : userPresences.values()) {
  786.                 if (presence.isAvailable()) {
  787.                     answer.add(presence.clone());
  788.                 }
  789.                 else {
  790.                     unavailable = presence;
  791.                 }
  792.             }
  793.             if (!answer.isEmpty()) {
  794.                 res = answer;
  795.             }
  796.             else if (unavailable != null) {
  797.                 res = Arrays.asList(unavailable.clone());
  798.             }
  799.             else {
  800.                 Presence presence = new Presence(Presence.Type.unavailable);
  801.                 presence.setFrom(user);
  802.                 res = Arrays.asList(presence);
  803.             }
  804.         }
  805.         return res;
  806.     }

  807.     /**
  808.      * Check if the given JID is subscribed to the user's presence.
  809.      * <p>
  810.      * If the JID is subscribed to the user's presence then it is allowed to see the presence and
  811.      * will get notified about presence changes. Also returns true, if the JID is the service
  812.      * name of the XMPP connection (the "XMPP domain"), i.e. the XMPP service is treated like
  813.      * having an implicit subscription to the users presence.
  814.      * </p>
  815.      * Note that if the roster is not loaded, then this method will always return false.
  816.      *
  817.      * @param jid
  818.      * @return true if the given JID is allowed to see the users presence.
  819.      * @since 4.1
  820.      */
  821.     public boolean isSubscribedToMyPresence(Jid jid) {
  822.         if (connection().getServiceName().equals(jid)) {
  823.             return true;
  824.         }
  825.         RosterEntry entry = getEntry(jid);
  826.         if (entry == null) {
  827.             return false;
  828.         }
  829.         switch (entry.getType()) {
  830.         case from:
  831.         case both:
  832.             return true;
  833.         default:
  834.             return false;
  835.         }
  836.     }

  837.     /**
  838.      * Sets if the roster will be loaded from the server when logging in. This
  839.      * is the common behaviour for clients but sometimes clients may want to differ this
  840.      * or just never do it if not interested in rosters.
  841.      *
  842.      * @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in.
  843.      */
  844.     public void setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) {
  845.         this.rosterLoadedAtLogin = rosterLoadedAtLogin;
  846.     }

  847.     /**
  848.      * Returns true if the roster will be loaded from the server when logging in. This
  849.      * is the common behavior for clients but sometimes clients may want to differ this
  850.      * or just never do it if not interested in rosters.
  851.      *
  852.      * @return true if the roster will be loaded from the server when logging in.
  853.      * @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster-login">RFC 6121 2.2 - Retrieving the Roster on Login</a>
  854.      */
  855.     public boolean isRosterLoadedAtLogin() {
  856.         return rosterLoadedAtLogin;
  857.     }

  858.     RosterStore getRosterStore() {
  859.         return rosterStore;
  860.     }

  861.     /**
  862.      * Returns the key to use in the presenceMap and entries Map for a fully qualified XMPP ID.
  863.      * The roster can contain any valid address format such us "domain/resource",
  864.      * "user@domain" or "user@domain/resource". If the roster contains an entry
  865.      * associated with the fully qualified XMPP ID then use the fully qualified XMPP
  866.      * ID as the key in presenceMap, otherwise use the bare address. Note: When the
  867.      * key in presenceMap is a fully qualified XMPP ID, the userPresences is useless
  868.      * since it will always contain one entry for the user.
  869.      *
  870.      * @param user the bare or fully qualified XMPP ID, e.g. jdoe@example.com or
  871.      *             jdoe@example.com/Work.
  872.      * @return the key to use in the presenceMap and entries Map for the fully qualified XMPP ID.
  873.      */
  874.     private Jid getMapKey(Jid user) {
  875.         if (user == null) {
  876.             return null;
  877.         }
  878.         if (entries.containsKey(user)) {
  879.             return user;
  880.         }
  881.         BareJid bareJid = user.asBareJidIfPossible();
  882.         if (bareJid != null) {
  883.             return bareJid;
  884.         }
  885.         // jid validate, log this case?
  886.         return user;
  887.     }

  888.     /**
  889.      * Changes the presence of available contacts offline by simulating an unavailable
  890.      * presence sent from the server. After a disconnection, every Presence is set
  891.      * to offline.
  892.      * @throws NotConnectedException
  893.      */
  894.     private void setOfflinePresencesAndResetLoaded() {
  895.         Presence packetUnavailable;
  896.         outerloop: for (Jid user : presenceMap.keySet()) {
  897.             Map<Resourcepart, Presence> resources = presenceMap.get(user);
  898.             if (resources != null) {
  899.                 for (Resourcepart resource : resources.keySet()) {
  900.                     packetUnavailable = new Presence(Presence.Type.unavailable);
  901.                     BareJid bareUserJid = user.asBareJidIfPossible();
  902.                     if (bareUserJid == null) {
  903.                         LOGGER.warning("Can not transform user JID to bare JID: '" + user + "'");
  904.                         continue;
  905.                     }
  906.                     packetUnavailable.setFrom(JidCreate.fullFrom(bareUserJid, resource));
  907.                     try {
  908.                         presencePacketListener.processPacket(packetUnavailable);
  909.                     }
  910.                     catch (NotConnectedException e) {
  911.                         throw new IllegalStateException(
  912.                                         "presencePakcetListener should never throw a NotConnectedException when processPacket is called with a presence of type unavailable",
  913.                                         e);
  914.                     }
  915.                     catch (InterruptedException e) {
  916.                         break outerloop;
  917.                     }
  918.                 }
  919.             }
  920.         }
  921.         loaded = false;
  922.     }

  923.     /**
  924.      * Fires roster changed event to roster listeners indicating that the
  925.      * specified collections of contacts have been added, updated or deleted
  926.      * from the roster.
  927.      *
  928.      * @param addedEntries   the collection of address of the added contacts.
  929.      * @param updatedEntries the collection of address of the updated contacts.
  930.      * @param deletedEntries the collection of address of the deleted contacts.
  931.      */
  932.     private void fireRosterChangedEvent(final Collection<Jid> addedEntries, final Collection<Jid> updatedEntries,
  933.                     final Collection<Jid> deletedEntries) {
  934.         synchronized (rosterListenersAndEntriesLock) {
  935.             for (RosterListener listener : rosterListeners) {
  936.                 if (!addedEntries.isEmpty()) {
  937.                     listener.entriesAdded(addedEntries);
  938.                 }
  939.                 if (!updatedEntries.isEmpty()) {
  940.                     listener.entriesUpdated(updatedEntries);
  941.                 }
  942.                 if (!deletedEntries.isEmpty()) {
  943.                     listener.entriesDeleted(deletedEntries);
  944.                 }
  945.             }
  946.         }
  947.     }

  948.     /**
  949.      * Fires roster presence changed event to roster listeners.
  950.      *
  951.      * @param presence the presence change.
  952.      */
  953.     private void fireRosterPresenceEvent(final Presence presence) {
  954.         synchronized (rosterListenersAndEntriesLock) {
  955.             for (RosterListener listener : rosterListeners) {
  956.                 listener.presenceChanged(presence);
  957.             }
  958.         }
  959.     }

  960.     private void addUpdateEntry(Collection<Jid> addedEntries, Collection<Jid> updatedEntries,
  961.                     Collection<Jid> unchangedEntries, RosterPacket.Item item, RosterEntry entry) {
  962.         RosterEntry oldEntry;
  963.         synchronized (rosterListenersAndEntriesLock) {
  964.             oldEntry = entries.put(item.getUser(), entry);
  965.         }
  966.         if (oldEntry == null) {
  967.             addedEntries.add(item.getUser());
  968.         }
  969.         else {
  970.             RosterPacket.Item oldItem = RosterEntry.toRosterItem(oldEntry);
  971.             if (!oldEntry.equalsDeep(entry) || !item.getGroupNames().equals(oldItem.getGroupNames())) {
  972.                 updatedEntries.add(item.getUser());
  973.             } else {
  974.                 // Record the entry as unchanged, so that it doesn't end up as deleted entry
  975.                 unchangedEntries.add(item.getUser());
  976.             }
  977.         }

  978.         // Mark the entry as unfiled if it does not belong to any groups.
  979.         if (item.getGroupNames().isEmpty()) {
  980.             unfiledEntries.add(entry);
  981.         }
  982.         else {
  983.             unfiledEntries.remove(entry);
  984.         }

  985.         // Add the entry/user to the groups
  986.         List<String> newGroupNames = new ArrayList<String>();
  987.         for (String groupName : item.getGroupNames()) {
  988.             // Add the group name to the list.
  989.             newGroupNames.add(groupName);

  990.             // Add the entry to the group.
  991.             RosterGroup group = getGroup(groupName);
  992.             if (group == null) {
  993.                 group = createGroup(groupName);
  994.                 groups.put(groupName, group);
  995.             }
  996.             // Add the entry.
  997.             group.addEntryLocal(entry);
  998.         }

  999.         // Remove user from the remaining groups.
  1000.         List<String> oldGroupNames = new ArrayList<String>();
  1001.         for (RosterGroup group: getGroups()) {
  1002.             oldGroupNames.add(group.getName());
  1003.         }
  1004.         oldGroupNames.removeAll(newGroupNames);

  1005.         for (String groupName : oldGroupNames) {
  1006.             RosterGroup group = getGroup(groupName);
  1007.             group.removeEntryLocal(entry);
  1008.             if (group.getEntryCount() == 0) {
  1009.                 groups.remove(groupName);
  1010.             }
  1011.         }
  1012.     }

  1013.     private void deleteEntry(Collection<Jid> deletedEntries, RosterEntry entry) {
  1014.         Jid user = entry.getUser();
  1015.         entries.remove(user);
  1016.         unfiledEntries.remove(entry);
  1017.         presenceMap.remove(user);
  1018.         deletedEntries.add(user);

  1019.         for (Entry<String,RosterGroup> e: groups.entrySet()) {
  1020.             RosterGroup group = e.getValue();
  1021.             group.removeEntryLocal(entry);
  1022.             if (group.getEntryCount() == 0) {
  1023.                 groups.remove(e.getKey());
  1024.             }
  1025.         }
  1026.     }


  1027.     /**
  1028.      * Removes all the groups with no entries.
  1029.      *
  1030.      * This is used by {@link RosterPushListener} and {@link RosterResultListener} to
  1031.      * cleanup groups after removing contacts.
  1032.      */
  1033.     private void removeEmptyGroups() {
  1034.         // We have to do this because RosterGroup.removeEntry removes the entry immediately
  1035.         // (locally) and the group could remain empty.
  1036.         // TODO Check the performance/logic for rosters with large number of groups
  1037.         for (RosterGroup group : getGroups()) {
  1038.             if (group.getEntryCount() == 0) {
  1039.                 groups.remove(group.getName());
  1040.             }
  1041.         }
  1042.     }

  1043.     /**
  1044.      * Ignore ItemTypes as of RFC 6121, 2.1.2.5.
  1045.      *
  1046.      * This is used by {@link RosterPushListener} and {@link RosterResultListener}.
  1047.      * */
  1048.     private static boolean hasValidSubscriptionType(RosterPacket.Item item) {
  1049.         switch (item.getItemType()) {
  1050.             case none:
  1051.             case from:
  1052.             case to:
  1053.             case both:
  1054.                 return true;
  1055.             default:
  1056.                 return false;
  1057.         }
  1058.     }

  1059.     /**
  1060.      * Check if the server supports roster versioning.
  1061.      *
  1062.      * @return true if the server supports roster versioning, false otherwise.
  1063.      */
  1064.     public boolean isRosterVersioningSupported() {
  1065.         return connection().hasFeature(RosterVer.ELEMENT, RosterVer.NAMESPACE);
  1066.     }

  1067.     /**
  1068.      * An enumeration for the subscription mode options.
  1069.      */
  1070.     public enum SubscriptionMode {

  1071.         /**
  1072.          * Automatically accept all subscription and unsubscription requests. This is
  1073.          * the default mode and is suitable for simple client. More complex client will
  1074.          * likely wish to handle subscription requests manually.
  1075.          */
  1076.         accept_all,

  1077.         /**
  1078.          * Automatically reject all subscription requests.
  1079.          */
  1080.         reject_all,

  1081.         /**
  1082.          * Subscription requests are ignored, which means they must be manually
  1083.          * processed by registering a listener for presence packets and then looking
  1084.          * for any presence requests that have the type Presence.Type.SUBSCRIBE or
  1085.          * Presence.Type.UNSUBSCRIBE.
  1086.          */
  1087.         manual
  1088.     }

  1089.     /**
  1090.      * Listens for all presence packets and processes them.
  1091.      */
  1092.     private class PresencePacketListener implements StanzaListener {

  1093.         /**
  1094.          * Retrieve the user presences (a map from resource to {@link Presence}) for a given key (usually a JID without
  1095.          * a resource). If the {@link #presenceMap} does not contain already a user presence map, then it will be
  1096.          * created.
  1097.          *
  1098.          * @param key the presence map key
  1099.          * @return the user presences
  1100.          */
  1101.         private Map<Resourcepart, Presence> getUserPresences(Jid key) {
  1102.             Map<Resourcepart, Presence> userPresences = presenceMap.get(key);
  1103.             if (userPresences == null) {
  1104.                 userPresences = new ConcurrentHashMap<>();
  1105.                 presenceMap.put(key, userPresences);
  1106.             }
  1107.             return userPresences;
  1108.         }

  1109.         @Override
  1110.         public void processPacket(Stanza packet) throws NotConnectedException, InterruptedException {
  1111.             final XMPPConnection connection = connection();
  1112.             Presence presence = (Presence) packet;
  1113.             Jid from = presence.getFrom();
  1114.             Resourcepart fromResource = from.getResourceOrNull();
  1115.             if (fromResource == null) {
  1116.                 fromResource = Resourcepart.EMPTY;
  1117.             }
  1118.             Jid key = getMapKey(from);
  1119.             Map<Resourcepart, Presence> userPresences;
  1120.             Presence response = null;

  1121.             // If an "available" presence, add it to the presence map. Each presence
  1122.             // map will hold for a particular user a map with the presence
  1123.             // packets saved for each resource.
  1124.             switch (presence.getType()) {
  1125.             case available:
  1126.                 // Get the user presence map
  1127.                 userPresences = getUserPresences(key);
  1128.                 // See if an offline presence was being stored in the map. If so, remove
  1129.                 // it since we now have an online presence.
  1130.                 userPresences.remove(Resourcepart.EMPTY);
  1131.                 // Add the new presence, using the resources as a key.
  1132.                 userPresences.put(fromResource, presence);
  1133.                 // If the user is in the roster, fire an event.
  1134.                 if (entries.containsKey(key)) {
  1135.                     fireRosterPresenceEvent(presence);
  1136.                 }
  1137.                 break;
  1138.             // If an "unavailable" packet.
  1139.             case unavailable:
  1140.                 // If no resource, this is likely an offline presence as part of
  1141.                 // a roster presence flood. In that case, we store it.
  1142.                 if (from.hasNoResource()) {
  1143.                     // Get the user presence map
  1144.                     userPresences = getUserPresences(key);
  1145.                     userPresences.put(Resourcepart.EMPTY, presence);
  1146.                 }
  1147.                 // Otherwise, this is a normal offline presence.
  1148.                 else if (presenceMap.get(key) != null) {
  1149.                     userPresences = presenceMap.get(key);
  1150.                     // Store the offline presence, as it may include extra information
  1151.                     // such as the user being on vacation.
  1152.                     userPresences.put(fromResource, presence);
  1153.                 }
  1154.                 // If the user is in the roster, fire an event.
  1155.                 if (entries.containsKey(key)) {
  1156.                     fireRosterPresenceEvent(presence);
  1157.                 }
  1158.                 break;
  1159.             case subscribe:
  1160.                 switch (subscriptionMode) {
  1161.                 case accept_all:
  1162.                     // Accept all subscription requests.
  1163.                     response = new Presence(Presence.Type.subscribed);
  1164.                     break;
  1165.                 case reject_all:
  1166.                     // Reject all subscription requests.
  1167.                     response = new Presence(Presence.Type.unsubscribed);
  1168.                     break;
  1169.                 case manual:
  1170.                 default:
  1171.                     // Otherwise, in manual mode so ignore.
  1172.                     break;
  1173.                 }
  1174.                 if (response != null) {
  1175.                     response.setTo(presence.getFrom());
  1176.                     connection.sendStanza(response);
  1177.                 }
  1178.                 break;
  1179.             case unsubscribe:
  1180.                 if (subscriptionMode != SubscriptionMode.manual) {
  1181.                     // Acknowledge and accept unsubscription notification so that the
  1182.                     // server will stop sending notifications saying that the contact
  1183.                     // has unsubscribed to our presence.
  1184.                     response = new Presence(Presence.Type.unsubscribed);
  1185.                     response.setTo(presence.getFrom());
  1186.                     connection.sendStanza(response);
  1187.                 }
  1188.                 // Otherwise, in manual mode so ignore.
  1189.                 break;
  1190.             // Error presence packets from a bare JID mean we invalidate all existing
  1191.             // presence info for the user.
  1192.             case error:
  1193.                 if (!from.isBareJid()) {
  1194.                     break;
  1195.                 }
  1196.                 userPresences = getUserPresences(key);
  1197.                 // Any other presence data is invalidated by the error packet.
  1198.                 userPresences.clear();

  1199.                 // Set the new presence using the empty resource as a key.
  1200.                 userPresences.put(Resourcepart.EMPTY, presence);
  1201.                 // If the user is in the roster, fire an event.
  1202.                 if (entries.containsKey(key)) {
  1203.                     fireRosterPresenceEvent(presence);
  1204.                 }
  1205.                 break;
  1206.             default:
  1207.                 break;
  1208.             }
  1209.         }
  1210.     }

  1211.     /**
  1212.      * Handles roster reults as described in RFC 6121 2.1.4
  1213.      */
  1214.     private class RosterResultListener implements StanzaListener {

  1215.         @Override
  1216.         public void processPacket(Stanza packet) {
  1217.             final XMPPConnection connection = connection();
  1218.             LOGGER.fine("RosterResultListener received stanza");
  1219.             Collection<Jid> addedEntries = new ArrayList<>();
  1220.             Collection<Jid> updatedEntries = new ArrayList<>();
  1221.             Collection<Jid> deletedEntries = new ArrayList<>();
  1222.             Collection<Jid> unchangedEntries = new ArrayList<>();

  1223.             if (packet instanceof RosterPacket) {
  1224.                 // Non-empty roster result. This stanza contains all the roster elements.
  1225.                 RosterPacket rosterPacket = (RosterPacket) packet;

  1226.                 // Ignore items without valid subscription type
  1227.                 ArrayList<Item> validItems = new ArrayList<RosterPacket.Item>();
  1228.                 for (RosterPacket.Item item : rosterPacket.getRosterItems()) {
  1229.                     if (hasValidSubscriptionType(item)) {
  1230.                         validItems.add(item);
  1231.                     }
  1232.                 }

  1233.                 for (RosterPacket.Item item : validItems) {
  1234.                     RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
  1235.                             item.getItemType(), item.getItemStatus(), Roster.this, connection);
  1236.                     addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
  1237.                 }

  1238.                 // Delete all entries which where not added or updated
  1239.                 Set<Jid> toDelete = new HashSet<>();
  1240.                 for (RosterEntry entry : entries.values()) {
  1241.                     toDelete.add(entry.getUser());
  1242.                 }
  1243.                 toDelete.removeAll(addedEntries);
  1244.                 toDelete.removeAll(updatedEntries);
  1245.                 toDelete.removeAll(unchangedEntries);
  1246.                 for (Jid user : toDelete) {
  1247.                     deleteEntry(deletedEntries, entries.get(user));
  1248.                 }

  1249.                 if (rosterStore != null) {
  1250.                     String version = rosterPacket.getVersion();
  1251.                     rosterStore.resetEntries(validItems, version);
  1252.                 }

  1253.                 removeEmptyGroups();
  1254.             }
  1255.             else {
  1256.                 // Empty roster result as defined in RFC6121 2.6.3. An empty roster result basically
  1257.                 // means that rosterver was used and the roster hasn't changed (much) since the
  1258.                 // version we presented the server. So we simply load the roster from the store and
  1259.                 // await possible further roster pushes.
  1260.                 for (RosterPacket.Item item : rosterStore.getEntries()) {
  1261.                     RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
  1262.                             item.getItemType(), item.getItemStatus(), Roster.this, connection);
  1263.                     addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
  1264.                 }
  1265.             }

  1266.             loaded = true;
  1267.             synchronized (Roster.this) {
  1268.                 Roster.this.notifyAll();
  1269.             }
  1270.             // Fire event for roster listeners.
  1271.             fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);

  1272.             // Call the roster loaded listeners after the roster events have been fired. This is
  1273.             // imporant because the user may call getEntriesAndAddListener() in onRosterLoaded(),
  1274.             // and if the order would be the other way around, the roster listener added by
  1275.             // getEntriesAndAddListener() would be invoked with information that was already
  1276.             // available at the time getEntriesAndAddListenr() was called.
  1277.             try {
  1278.                 synchronized (rosterLoadedListeners) {
  1279.                     for (RosterLoadedListener rosterLoadedListener : rosterLoadedListeners) {
  1280.                         rosterLoadedListener.onRosterLoaded(Roster.this);
  1281.                     }
  1282.                 }
  1283.             }
  1284.             catch (Exception e) {
  1285.                 LOGGER.log(Level.WARNING, "RosterLoadedListener threw exception", e);
  1286.             }
  1287.         }
  1288.     }

  1289.     /**
  1290.      * Listens for all roster pushes and processes them.
  1291.      */
  1292.     private class RosterPushListener extends AbstractIqRequestHandler {

  1293.         private RosterPushListener() {
  1294.             super(RosterPacket.ELEMENT, RosterPacket.NAMESPACE, Type.set, Mode.sync);
  1295.         }

  1296.         @Override
  1297.         public IQ handleIQRequest(IQ iqRequest) {
  1298.             final XMPPConnection connection = connection();
  1299.             RosterPacket rosterPacket = (RosterPacket) iqRequest;

  1300.             // Roster push (RFC 6121, 2.1.6)
  1301.             // A roster push with a non-empty from not matching our address MUST be ignored
  1302.             BareJid jid = connection.getUser().asBareJid();
  1303.             Jid from = rosterPacket.getFrom();
  1304.             if (from != null && !from.equals(jid)) {
  1305.                 LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from
  1306.                                 + "'");
  1307.                 return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.service_unavailable));
  1308.             }

  1309.             // A roster push must contain exactly one entry
  1310.             Collection<Item> items = rosterPacket.getRosterItems();
  1311.             if (items.size() != 1) {
  1312.                 LOGGER.warning("Ignoring roster push with not exaclty one entry. size=" + items.size());
  1313.                 return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.bad_request));
  1314.             }

  1315.             Collection<Jid> addedEntries = new ArrayList<>();
  1316.             Collection<Jid> updatedEntries = new ArrayList<>();
  1317.             Collection<Jid> deletedEntries = new ArrayList<>();
  1318.             Collection<Jid> unchangedEntries = new ArrayList<>();

  1319.             // We assured above that the size of items is exaclty 1, therefore we are able to
  1320.             // safely retrieve this single item here.
  1321.             Item item = items.iterator().next();
  1322.             RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
  1323.                             item.getItemType(), item.getItemStatus(), Roster.this, connection);
  1324.             String version = rosterPacket.getVersion();

  1325.             if (item.getItemType().equals(RosterPacket.ItemType.remove)) {
  1326.                 deleteEntry(deletedEntries, entry);
  1327.                 if (rosterStore != null) {
  1328.                     rosterStore.removeEntry(entry.getUser(), version);
  1329.                 }
  1330.             }
  1331.             else if (hasValidSubscriptionType(item)) {
  1332.                 addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
  1333.                 if (rosterStore != null) {
  1334.                     rosterStore.addEntry(item, version);
  1335.                 }
  1336.             }

  1337.             removeEmptyGroups();

  1338.             // Fire event for roster listeners.
  1339.             fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);

  1340.             return IQ.createResultIQ(rosterPacket);
  1341.         }
  1342.     }
  1343. }