001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack.roster;
019
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Set;
031import java.util.WeakHashMap;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.CopyOnWriteArraySet;
034import java.util.logging.Level;
035import java.util.logging.Logger;
036
037import org.jivesoftware.smack.AbstractConnectionClosedListener;
038import org.jivesoftware.smack.ConnectionCreationListener;
039import org.jivesoftware.smack.ExceptionCallback;
040import org.jivesoftware.smack.Manager;
041import org.jivesoftware.smack.StanzaListener;
042import org.jivesoftware.smack.SmackException;
043import org.jivesoftware.smack.XMPPConnection;
044import org.jivesoftware.smack.SmackException.NoResponseException;
045import org.jivesoftware.smack.SmackException.NotConnectedException;
046import org.jivesoftware.smack.SmackException.NotLoggedInException;
047import org.jivesoftware.smack.XMPPConnectionRegistry;
048import org.jivesoftware.smack.XMPPException.XMPPErrorException;
049import org.jivesoftware.smack.filter.StanzaFilter;
050import org.jivesoftware.smack.filter.StanzaTypeFilter;
051import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
052import org.jivesoftware.smack.packet.IQ;
053import org.jivesoftware.smack.packet.IQ.Type;
054import org.jivesoftware.smack.packet.Stanza;
055import org.jivesoftware.smack.packet.Presence;
056import org.jivesoftware.smack.packet.XMPPError;
057import org.jivesoftware.smack.packet.XMPPError.Condition;
058import org.jivesoftware.smack.roster.packet.RosterPacket;
059import org.jivesoftware.smack.roster.packet.RosterVer;
060import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
061import org.jivesoftware.smack.roster.rosterstore.RosterStore;
062import org.jivesoftware.smack.util.Objects;
063import org.jxmpp.util.XmppStringUtils;
064
065/**
066 * Represents a user's roster, which is the collection of users a person receives
067 * presence updates for. Roster items are categorized into groups for easier management.<p>
068 * <p/>
069 * Others users may attempt to subscribe to this user using a subscription request. Three
070 * modes are supported for handling these requests: <ul>
071 * <li>{@link SubscriptionMode#accept_all accept_all} -- accept all subscription requests.</li>
072 * <li>{@link SubscriptionMode#reject_all reject_all} -- reject all subscription requests.</li>
073 * <li>{@link SubscriptionMode#manual manual} -- manually process all subscription requests.</li>
074 * </ul>
075 *
076 * @author Matt Tucker
077 * @see #getInstanceFor(XMPPConnection)
078 */
079public class Roster extends Manager {
080
081    private static final Logger LOGGER = Logger.getLogger(Roster.class.getName());
082
083    static {
084        XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
085            @Override
086            public void connectionCreated(XMPPConnection connection) {
087                getInstanceFor(connection);
088            }
089        });
090    }
091
092    private static final Map<XMPPConnection, Roster> INSTANCES = new WeakHashMap<>();
093
094    /**
095     * Returns the roster for the user.
096     * <p>
097     * This method will never return <code>null</code>, instead if the user has not yet logged into
098     * the server or is logged in anonymously all modifying methods of the returned roster object
099     * like {@link Roster#createEntry(String, String, String[])},
100     * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing
101     * {@link RosterListener}s will throw an IllegalStateException.
102     * 
103     * @return the user's roster.
104     * @throws IllegalStateException if the connection is anonymous
105     */
106    public static synchronized Roster getInstanceFor(XMPPConnection connection) {
107        Roster roster = INSTANCES.get(connection);
108        if (roster == null) {
109            roster = new Roster(connection);
110            INSTANCES.put(connection, roster);
111        }
112        return roster;
113    }
114
115    private static final StanzaFilter PRESENCE_PACKET_FILTER = StanzaTypeFilter.PRESENCE;
116
117    private static boolean rosterLoadedAtLoginDefault = true;
118
119    /**
120     * The default subscription processing mode to use when a Roster is created. By default
121     * all subscription requests are automatically accepted.
122     */
123    private static SubscriptionMode defaultSubscriptionMode = SubscriptionMode.accept_all;
124
125    private RosterStore rosterStore;
126    private final Map<String, RosterGroup> groups = new ConcurrentHashMap<String, RosterGroup>();
127
128    /**
129     * Concurrent hash map from JID to its roster entry.
130     */
131    private final Map<String,RosterEntry> entries = new ConcurrentHashMap<String,RosterEntry>();
132
133    private final Set<RosterEntry> unfiledEntries = new CopyOnWriteArraySet<>();
134    private final Set<RosterListener> rosterListeners = new LinkedHashSet<>();
135    private final Map<String, Map<String, Presence>> presenceMap = new ConcurrentHashMap<String, Map<String, Presence>>();
136
137    /**
138     * Listeners called when the Roster was loaded.
139     */
140    private final Set<RosterLoadedListener> rosterLoadedListeners = new LinkedHashSet<>();
141
142    /**
143     * Mutually exclude roster listener invocation and changing the {@link entries} map. Also used
144     * to synchronize access to either the roster listeners or the entries map.
145     */
146    private final Object rosterListenersAndEntriesLock = new Object();
147
148    // The roster is marked as initialized when at least a single roster packet
149    // has been received and processed.
150    private boolean loaded = false;
151
152    private final PresencePacketListener presencePacketListener = new PresencePacketListener();
153
154    /**
155     * 
156     */
157    private boolean rosterLoadedAtLogin = rosterLoadedAtLoginDefault;
158
159    private SubscriptionMode subscriptionMode = getDefaultSubscriptionMode();
160
161    /**
162     * Returns the default subscription processing mode to use when a new Roster is created. The
163     * subscription processing mode dictates what action Smack will take when subscription
164     * requests from other users are made. The default subscription mode
165     * is {@link SubscriptionMode#accept_all}.
166     *
167     * @return the default subscription mode to use for new Rosters
168     */
169    public static SubscriptionMode getDefaultSubscriptionMode() {
170        return defaultSubscriptionMode;
171    }
172
173    /**
174     * Sets the default subscription processing mode to use when a new Roster is created. The
175     * subscription processing mode dictates what action Smack will take when subscription
176     * requests from other users are made. The default subscription mode
177     * is {@link SubscriptionMode#accept_all}.
178     *
179     * @param subscriptionMode the default subscription mode to use for new Rosters.
180     */
181    public static void setDefaultSubscriptionMode(SubscriptionMode subscriptionMode) {
182        defaultSubscriptionMode = subscriptionMode;
183    }
184
185    /**
186     * Creates a new roster.
187     *
188     * @param connection an XMPP connection.
189     */
190    private Roster(final XMPPConnection connection) {
191        super(connection);
192
193        // Note that we use sync packet listeners because RosterListeners should be invoked in the same order as the
194        // roster stanzas arrive.
195        // Listen for any roster packets.
196        connection.registerIQRequestHandler(new RosterPushListener());
197        // Listen for any presence packets.
198        connection.addSyncStanzaListener(presencePacketListener, PRESENCE_PACKET_FILTER);
199
200        // Listen for connection events
201        connection.addConnectionListener(new AbstractConnectionClosedListener() {
202
203            @Override
204            public void authenticated(XMPPConnection connection, boolean resumed) {
205                // Anonymous users can't have a roster, but it is possible that a Roster instance is
206                // retrieved if getRoster() is called *before* connect(). So we have to check here
207                // again if it's an anonymous connection.
208                if (connection.isAnonymous())
209                    return;
210                if (!isRosterLoadedAtLogin())
211                    return;
212                // We are done here if the connection was resumed
213                if (resumed) {
214                    return;
215                }
216                try {
217                    Roster.this.reload();
218                }
219                catch (SmackException e) {
220                    LOGGER.log(Level.SEVERE, "Could not reload Roster", e);
221                    return;
222                }
223            }
224
225            @Override
226            public void connectionTerminated() {
227                // Changes the presence available contacts to unavailable
228                setOfflinePresencesAndResetLoaded();
229            }
230
231        });
232        // If the connection is already established, call reload
233        if (connection.isAuthenticated()) {
234            try {
235                reload();
236            }
237            catch (SmackException e) {
238                LOGGER.log(Level.SEVERE, "Could not reload Roster", e);
239            }
240        }
241    }
242
243    /**
244     * Returns the subscription processing mode, which dictates what action
245     * Smack will take when subscription requests from other users are made.
246     * The default subscription mode is {@link SubscriptionMode#accept_all}.<p>
247     * <p/>
248     * If using the manual mode, a PacketListener should be registered that
249     * listens for Presence packets that have a type of
250     * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
251     *
252     * @return the subscription mode.
253     */
254    public SubscriptionMode getSubscriptionMode() {
255        return subscriptionMode;
256    }
257
258    /**
259     * Sets the subscription processing mode, which dictates what action
260     * Smack will take when subscription requests from other users are made.
261     * The default subscription mode is {@link SubscriptionMode#accept_all}.<p>
262     * <p/>
263     * If using the manual mode, a PacketListener should be registered that
264     * listens for Presence packets that have a type of
265     * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}.
266     *
267     * @param subscriptionMode the subscription mode.
268     */
269    public void setSubscriptionMode(SubscriptionMode subscriptionMode) {
270        this.subscriptionMode = subscriptionMode;
271    }
272
273    /**
274     * Reloads the entire roster from the server. This is an asynchronous operation,
275     * which means the method will return immediately, and the roster will be
276     * reloaded at a later point when the server responds to the reload request.
277     * @throws NotLoggedInException If not logged in.
278     * @throws NotConnectedException 
279     */
280    public void reload() throws NotLoggedInException, NotConnectedException{
281        final XMPPConnection connection = connection();
282        if (!connection.isAuthenticated()) {
283            throw new NotLoggedInException();
284        }
285        if (connection.isAnonymous()) {
286            throw new IllegalStateException("Anonymous users can't have a roster.");
287        }
288
289        RosterPacket packet = new RosterPacket();
290        if (rosterStore != null && isRosterVersioningSupported()) {
291            packet.setVersion(rosterStore.getRosterVersion());
292        }
293        connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
294            @Override
295            public void processException(Exception exception) {
296                LOGGER.log(Level.SEVERE, "Exception reloading roster" , exception);
297            }
298        });
299    }
300
301    /**
302     * Reload the roster and block until it is reloaded.
303     *
304     * @throws NotLoggedInException
305     * @throws NotConnectedException
306     * @since 4.1
307     */
308    public void reloadAndWait() throws NotLoggedInException, NotConnectedException {
309        reload();
310        waitUntilLoaded();
311    }
312 
313    /**
314     * Set the roster store, may cause a roster reload
315     *
316     * @param rosterStore
317     * @return true if the roster reload was initiated, false otherwise.
318     * @since 4.1
319     */
320    public boolean setRosterStore(RosterStore rosterStore) {
321        this.rosterStore = rosterStore;
322        try {
323            reload();
324        }
325        catch (NotLoggedInException | NotConnectedException e) {
326            LOGGER.log(Level.FINER, "Could not reload roster", e);
327            return false;
328        }
329        return true;
330    }
331
332    protected boolean waitUntilLoaded() {
333        final XMPPConnection connection = connection();
334        while (!loaded) {
335            long waitTime = connection.getPacketReplyTimeout();
336            long start = System.currentTimeMillis();
337            if (waitTime <= 0) {
338                break;
339            }
340            try {
341                synchronized (this) {
342                    if (!loaded) {
343                        wait(waitTime);
344                    }
345                }
346            }
347            catch (InterruptedException e) {
348                LOGGER.log(Level.FINE, "interrupted", e);
349                break;
350            }
351            long now = System.currentTimeMillis();
352            waitTime -= now - start;
353            start = now;
354        }
355        return isLoaded();
356    }
357
358    /**
359     * Check if the roster is loaded.
360     *
361     * @return true if the roster is loaded.
362     * @since 4.1
363     */
364    public boolean isLoaded() {
365        return loaded;
366    }
367
368    /**
369     * Adds a listener to this roster. The listener will be fired anytime one or more
370     * changes to the roster are pushed from the server.
371     *
372     * @param rosterListener a roster listener.
373     * @return true if the listener was not already added.
374     * @see #getEntriesAndAddListener(RosterListener, RosterEntries)
375     */
376    public boolean addRosterListener(RosterListener rosterListener) {
377        synchronized (rosterListenersAndEntriesLock) {
378            return rosterListeners.add(rosterListener);
379        }
380    }
381
382    /**
383     * Removes a listener from this roster. The listener will be fired anytime one or more
384     * changes to the roster are pushed from the server.
385     *
386     * @param rosterListener a roster listener.
387     * @return true if the listener was active and got removed.
388     */
389    public boolean removeRosterListener(RosterListener rosterListener) {
390        synchronized (rosterListenersAndEntriesLock) {
391            return rosterListeners.remove(rosterListener);
392        }
393    }
394
395    /**
396     * Add a roster loaded listener.
397     *
398     * @param rosterLoadedListener the listener to add.
399     * @return true if the listener was not already added.
400     * @see RosterLoadedListener
401     * @since 4.1
402     */
403    public boolean addRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
404        synchronized (rosterLoadedListener) {
405            return rosterLoadedListeners.add(rosterLoadedListener);
406        }
407    }
408
409    /**
410     * Remove a roster loaded listener.
411     *
412     * @param rosterLoadedListener the listener to remove.
413     * @return true if the listener was active and got removed.
414     * @see RosterLoadedListener
415     * @since 4.1
416     */
417    public boolean removeRosterLoadedListener(RosterLoadedListener rosterLoadedListener) {
418        synchronized (rosterLoadedListener) {
419            return rosterLoadedListeners.remove(rosterLoadedListener);
420        }
421    }
422
423    /**
424     * Creates a new group.<p>
425     * <p/>
426     * Note: you must add at least one entry to the group for the group to be kept
427     * after a logout/login. This is due to the way that XMPP stores group information.
428     *
429     * @param name the name of the group.
430     * @return a new group, or null if the group already exists
431     * @throws IllegalStateException if logged in anonymously
432     */
433    public RosterGroup createGroup(String name) {
434        final XMPPConnection connection = connection();
435        if (connection.isAnonymous()) {
436            throw new IllegalStateException("Anonymous users can't have a roster.");
437        }
438        if (groups.containsKey(name)) {
439            return groups.get(name);
440        }
441        
442        RosterGroup group = new RosterGroup(name, connection);
443        groups.put(name, group);
444        return group;
445    }
446
447    /**
448     * Creates a new roster entry and presence subscription. The server will asynchronously
449     * update the roster with the subscription status.
450     *
451     * @param user   the user. (e.g. johndoe@jabber.org)
452     * @param name   the nickname of the user.
453     * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the
454     *               the roster entry won't belong to a group.
455     * @throws NoResponseException if there was no response from the server.
456     * @throws XMPPErrorException if an XMPP exception occurs.
457     * @throws NotLoggedInException If not logged in.
458     * @throws NotConnectedException 
459     */
460    public void createEntry(String user, String name, String[] groups) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException {
461        final XMPPConnection connection = connection();
462        if (!connection.isAuthenticated()) {
463            throw new NotLoggedInException();
464        }
465        if (connection.isAnonymous()) {
466            throw new IllegalStateException("Anonymous users can't have a roster.");
467        }
468
469        // Create and send roster entry creation packet.
470        RosterPacket rosterPacket = new RosterPacket();
471        rosterPacket.setType(IQ.Type.set);
472        RosterPacket.Item item = new RosterPacket.Item(user, name);
473        if (groups != null) {
474            for (String group : groups) {
475                if (group != null && group.trim().length() > 0) {
476                    item.addGroupName(group);
477                }
478            }
479        }
480        rosterPacket.addRosterItem(item);
481        connection.createPacketCollectorAndSend(rosterPacket).nextResultOrThrow();
482
483        // Create a presence subscription packet and send.
484        Presence presencePacket = new Presence(Presence.Type.subscribe);
485        presencePacket.setTo(user);
486        connection.sendStanza(presencePacket);
487    }
488
489    /**
490     * Removes a roster entry from the roster. The roster entry will also be removed from the
491     * unfiled entries or from any roster group where it could belong and will no longer be part
492     * of the roster. Note that this is a synchronous call -- Smack must wait for the server
493     * to send an updated subscription status.
494     *
495     * @param entry a roster entry.
496     * @throws XMPPErrorException if an XMPP error occurs.
497     * @throws NotLoggedInException if not logged in.
498     * @throws NoResponseException SmackException if there was no response from the server.
499     * @throws NotConnectedException 
500     * @throws IllegalStateException if connection is not logged in or logged in anonymously
501     */
502    public void removeEntry(RosterEntry entry) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException {
503        final XMPPConnection connection = connection();
504        if (!connection.isAuthenticated()) {
505            throw new NotLoggedInException();
506        }
507        if (connection.isAnonymous()) {
508            throw new IllegalStateException("Anonymous users can't have a roster.");
509        }
510
511        // Only remove the entry if it's in the entry list.
512        // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet)
513        if (!entries.containsKey(entry.getUser())) {
514            return;
515        }
516        RosterPacket packet = new RosterPacket();
517        packet.setType(IQ.Type.set);
518        RosterPacket.Item item = RosterEntry.toRosterItem(entry);
519        // Set the item type as REMOVE so that the server will delete the entry
520        item.setItemType(RosterPacket.ItemType.remove);
521        packet.addRosterItem(item);
522        connection.createPacketCollectorAndSend(packet).nextResultOrThrow();
523    }
524
525    /**
526     * Returns a count of the entries in the roster.
527     *
528     * @return the number of entries in the roster.
529     */
530    public int getEntryCount() {
531        return getEntries().size();
532    }
533
534    /**
535     * Add a roster listener and invoke the roster entries with all entries of the roster.
536     * <p>
537     * The method guarantees that the listener is only invoked after
538     * {@link RosterEntries#rosterEntires(Collection)} has been invoked, and that all roster events
539     * that happen while <code>rosterEntires(Collection) </code> is called are queued until the
540     * method returns.
541     * </p>
542     * <p>
543     * This guarantee makes this the ideal method to e.g. populate a UI element with the roster while
544     * installing a {@link RosterListener} to listen for subsequent roster events.
545     * </p>
546     *
547     * @param rosterListener the listener to install
548     * @param rosterEntries the roster entries callback interface
549     * @since 4.1
550     */
551    public void getEntriesAndAddListener(RosterListener rosterListener, RosterEntries rosterEntries) {
552        Objects.requireNonNull(rosterListener, "listener must not be null");
553        Objects.requireNonNull(rosterEntries, "rosterEntries must not be null");
554
555        synchronized (rosterListenersAndEntriesLock) {
556            rosterEntries.rosterEntires(entries.values());
557            addRosterListener(rosterListener);
558        }
559    }
560
561    /**
562     * Returns a set of all entries in the roster, including entries
563     * that don't belong to any groups.
564     *
565     * @return all entries in the roster.
566     */
567    public Set<RosterEntry> getEntries() {
568        Set<RosterEntry> allEntries;
569        synchronized (rosterListenersAndEntriesLock) {
570            allEntries = new HashSet<>(entries.size());
571            for (RosterEntry entry : entries.values()) {
572                allEntries.add(entry);
573            }
574        }
575        return allEntries;
576    }
577
578    /**
579     * Returns a count of the unfiled entries in the roster. An unfiled entry is
580     * an entry that doesn't belong to any groups.
581     *
582     * @return the number of unfiled entries in the roster.
583     */
584    public int getUnfiledEntryCount() {
585        return unfiledEntries.size();
586    }
587
588    /**
589     * Returns an unmodifiable set for the unfiled roster entries. An unfiled entry is
590     * an entry that doesn't belong to any groups.
591     *
592     * @return the unfiled roster entries.
593     */
594    public Set<RosterEntry> getUnfiledEntries() {
595        return Collections.unmodifiableSet(unfiledEntries);
596    }
597
598    /**
599     * Returns the roster entry associated with the given XMPP address or
600     * <tt>null</tt> if the user is not an entry in the roster.
601     *
602     * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be
603     *             in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource").
604     * @return the roster entry or <tt>null</tt> if it does not exist.
605     */
606    public RosterEntry getEntry(String user) {
607        if (user == null) {
608            return null;
609        }
610        String key = getMapKey(user);
611        return entries.get(key);
612    }
613
614    /**
615     * Returns true if the specified XMPP address is an entry in the roster.
616     *
617     * @param user the XMPP address of the user (eg "jsmith@example.com"). The
618     *             address could be in any valid format (e.g. "domain/resource",
619     *             "user@domain" or "user@domain/resource").
620     * @return true if the XMPP address is an entry in the roster.
621     */
622    public boolean contains(String user) {
623        return getEntry(user) != null;
624    }
625
626    /**
627     * Returns the roster group with the specified name, or <tt>null</tt> if the
628     * group doesn't exist.
629     *
630     * @param name the name of the group.
631     * @return the roster group with the specified name.
632     */
633    public RosterGroup getGroup(String name) {
634        return groups.get(name);
635    }
636
637    /**
638     * Returns the number of the groups in the roster.
639     *
640     * @return the number of groups in the roster.
641     */
642    public int getGroupCount() {
643        return groups.size();
644    }
645
646    /**
647     * Returns an unmodifiable collections of all the roster groups.
648     *
649     * @return an iterator for all roster groups.
650     */
651    public Collection<RosterGroup> getGroups() {
652        return Collections.unmodifiableCollection(groups.values());
653    }
654
655    /**
656     * Returns the presence info for a particular user. If the user is offline, or
657     * if no presence data is available (such as when you are not subscribed to the
658     * user's presence updates), unavailable presence will be returned.
659     * <p>
660     * If the user has several presences (one for each resource), then the presence with
661     * highest priority will be returned. If multiple presences have the same priority,
662     * the one with the "most available" presence mode will be returned. In order,
663     * that's {@link org.jivesoftware.smack.packet.Presence.Mode#chat free to chat},
664     * {@link org.jivesoftware.smack.packet.Presence.Mode#available available},
665     * {@link org.jivesoftware.smack.packet.Presence.Mode#away away},
666     * {@link org.jivesoftware.smack.packet.Presence.Mode#xa extended away}, and
667     * {@link org.jivesoftware.smack.packet.Presence.Mode#dnd do not disturb}.<p>
668     * </p>
669     * <p>
670     * Note that presence information is received asynchronously. So, just after logging
671     * in to the server, presence values for users in the roster may be unavailable
672     * even if they are actually online. In other words, the value returned by this
673     * method should only be treated as a snapshot in time, and may not accurately reflect
674     * other user's presence instant by instant. If you need to track presence over time,
675     * such as when showing a visual representation of the roster, consider using a
676     * {@link RosterListener}.
677     * </p>
678     *
679     * @param user an XMPP ID. The address could be in any valid format (e.g.
680     *             "domain/resource", "user@domain" or "user@domain/resource"). Any resource
681     *             information that's part of the ID will be discarded.
682     * @return the user's current presence, or unavailable presence if the user is offline
683     *         or if no presence information is available..
684     */
685    public Presence getPresence(String user) {
686        String key = getMapKey(XmppStringUtils.parseBareJid(user));
687        Map<String, Presence> userPresences = presenceMap.get(key);
688        if (userPresences == null) {
689            Presence presence = new Presence(Presence.Type.unavailable);
690            presence.setFrom(user);
691            return presence;
692        }
693        else {
694            // Find the resource with the highest priority
695            // Might be changed to use the resource with the highest availability instead.
696            Presence presence = null;
697            // This is used in case no available presence is found
698            Presence unavailable = null;
699
700            for (String resource : userPresences.keySet()) {
701                Presence p = userPresences.get(resource);
702                if (!p.isAvailable()) {
703                    unavailable = p;
704                    continue;
705                }
706                // Chose presence with highest priority first.
707                if (presence == null || p.getPriority() > presence.getPriority()) {
708                    presence = p;
709                }
710                // If equal priority, choose "most available" by the mode value.
711                else if (p.getPriority() == presence.getPriority()) {
712                    Presence.Mode pMode = p.getMode();
713                    // Default to presence mode of available.
714                    if (pMode == null) {
715                        pMode = Presence.Mode.available;
716                    }
717                    Presence.Mode presenceMode = presence.getMode();
718                    // Default to presence mode of available.
719                    if (presenceMode == null) {
720                        presenceMode = Presence.Mode.available;
721                    }
722                    if (pMode.compareTo(presenceMode) < 0) {
723                        presence = p;
724                    }
725                }
726            }
727            if (presence == null) {
728                if (unavailable != null) {
729                    return unavailable.clone();
730                }
731                else {
732                    presence = new Presence(Presence.Type.unavailable);
733                    presence.setFrom(user);
734                    return presence;
735                }
736            }
737            else {
738                return presence.clone();
739            }
740        }
741    }
742
743    /**
744     * Returns the presence info for a particular user's resource, or unavailable presence
745     * if the user is offline or if no presence information is available, such as
746     * when you are not subscribed to the user's presence updates.
747     *
748     * @param userWithResource a fully qualified XMPP ID including a resource (user@domain/resource).
749     * @return the user's current presence, or unavailable presence if the user is offline
750     *         or if no presence information is available.
751     */
752    public Presence getPresenceResource(String userWithResource) {
753        String key = getMapKey(userWithResource);
754        String resource = XmppStringUtils.parseResource(userWithResource);
755        Map<String, Presence> userPresences = presenceMap.get(key);
756        if (userPresences == null) {
757            Presence presence = new Presence(Presence.Type.unavailable);
758            presence.setFrom(userWithResource);
759            return presence;
760        }
761        else {
762            Presence presence = userPresences.get(resource);
763            if (presence == null) {
764                presence = new Presence(Presence.Type.unavailable);
765                presence.setFrom(userWithResource);
766                return presence;
767            }
768            else {
769                return presence.clone();
770            }
771        }
772    }
773
774    /**
775     * Returns a List of Presence objects for all of a user's current presences if no presence information is available,
776     * such as when you are not subscribed to the user's presence updates.
777     *
778     * @param bareJid an XMPP ID, e.g. jdoe@example.com.
779     * @return a List of Presence objects for all the user's current presences, or an unavailable presence if no
780     *         presence information is available.
781     */
782    public List<Presence> getAllPresences(String bareJid) {
783        Map<String, Presence> userPresences = presenceMap.get(getMapKey(bareJid));
784        List<Presence> res;
785        if (userPresences == null) {
786            // Create an unavailable presence if none was found
787            Presence unavailable = new Presence(Presence.Type.unavailable);
788            unavailable.setFrom(bareJid);
789            res = new ArrayList<>(Arrays.asList(unavailable));
790        } else {
791            res = new ArrayList<>(userPresences.values().size());
792            for (Presence presence : userPresences.values()) {
793                res.add(presence.clone());
794            }
795        }
796        return res;
797    }
798
799    /**
800     * Returns a List of all <b>available</b> Presence Objects for the given bare JID. If there are no available
801     * presences, then the empty list will be returned.
802     *
803     * @param bareJid the bare JID from which the presences should be retrieved.
804     * @return available presences for the bare JID.
805     */
806    public List<Presence> getAvailablePresences(String bareJid) {
807        List<Presence> allPresences = getAllPresences(bareJid);
808        List<Presence> res = new ArrayList<>(allPresences.size());
809        for (Presence presence : allPresences) {
810            if (presence.isAvailable()) {
811                // No need to clone presence here, getAllPresences already returns clones
812                res.add(presence);
813            }
814        }
815        return res;
816    }
817
818    /**
819     * Returns a List of Presence objects for all of a user's current presences
820     * or an unavailable presence if the user is unavailable (offline) or if no presence
821     * information is available, such as when you are not subscribed to the user's presence
822     * updates.
823     *
824     * @param user an XMPP ID, e.g. jdoe@example.com.
825     * @return a List of Presence objects for all the user's current presences,
826     *         or an unavailable presence if the user is offline or if no presence information
827     *         is available.
828     */
829    public List<Presence> getPresences(String user) {
830        List<Presence> res;
831        String key = getMapKey(user);
832        Map<String, Presence> userPresences = presenceMap.get(key);
833        if (userPresences == null) {
834            Presence presence = new Presence(Presence.Type.unavailable);
835            presence.setFrom(user);
836            res = Arrays.asList(presence);
837        }
838        else {
839            List<Presence> answer = new ArrayList<Presence>();
840            // Used in case no available presence is found
841            Presence unavailable = null;
842            for (Presence presence : userPresences.values()) {
843                if (presence.isAvailable()) {
844                    answer.add(presence.clone());
845                }
846                else {
847                    unavailable = presence;
848                }
849            }
850            if (!answer.isEmpty()) {
851                res = answer;
852            }
853            else if (unavailable != null) {
854                res = Arrays.asList(unavailable.clone());
855            }
856            else {
857                Presence presence = new Presence(Presence.Type.unavailable);
858                presence.setFrom(user);
859                res = Arrays.asList(presence);
860            }
861        }
862        return res;
863    }
864
865    /**
866     * Check if the given JID is subscribed to the user's presence.
867     * <p>
868     * If the JID is subscribed to the user's presence then it is allowed to see the presence and
869     * will get notified about presence changes. Also returns true, if the JID is the service
870     * name of the XMPP connection (the "XMPP domain"), i.e. the XMPP service is treated like
871     * having an implicit subscription to the users presence.
872     * </p>
873     * Note that if the roster is not loaded, then this method will always return false.
874     * 
875     * @param jid
876     * @return true if the given JID is allowed to see the users presence.
877     * @since 4.1
878     */
879    public boolean isSubscribedToMyPresence(String jid) {
880        if (connection().getServiceName().equals(jid)) {
881            return true;
882        }
883        RosterEntry entry = getEntry(jid);
884        if (entry == null) {
885            return false;
886        }
887        switch (entry.getType()) {
888        case from:
889        case both:
890            return true;
891        default:
892            return false;
893        }
894    }
895
896    /**
897     * Sets if the roster will be loaded from the server when logging in. This
898     * is the common behaviour for clients but sometimes clients may want to differ this
899     * or just never do it if not interested in rosters.
900     *
901     * @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in.
902     */
903    public void setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) {
904        this.rosterLoadedAtLogin = rosterLoadedAtLogin;
905    }
906
907    /**
908     * Returns true if the roster will be loaded from the server when logging in. This
909     * is the common behavior for clients but sometimes clients may want to differ this
910     * or just never do it if not interested in rosters.
911     *
912     * @return true if the roster will be loaded from the server when logging in.
913     * @see <a href="http://xmpp.org/rfcs/rfc6121.html#roster-login">RFC 6121 2.2 - Retrieving the Roster on Login</a>
914     */
915    public boolean isRosterLoadedAtLogin() {
916        return rosterLoadedAtLogin;
917    }
918
919    RosterStore getRosterStore() {
920        return rosterStore;
921    }
922
923    /**
924     * Returns the key to use in the presenceMap and entries Map for a fully qualified XMPP ID.
925     * The roster can contain any valid address format such us "domain/resource",
926     * "user@domain" or "user@domain/resource". If the roster contains an entry
927     * associated with the fully qualified XMPP ID then use the fully qualified XMPP
928     * ID as the key in presenceMap, otherwise use the bare address. Note: When the
929     * key in presenceMap is a fully qualified XMPP ID, the userPresences is useless
930     * since it will always contain one entry for the user.
931     *
932     * @param user the bare or fully qualified XMPP ID, e.g. jdoe@example.com or
933     *             jdoe@example.com/Work.
934     * @return the key to use in the presenceMap and entries Map for the fully qualified XMPP ID.
935     */
936    private String getMapKey(String user) {
937        if (user == null) {
938            return null;
939        }
940        if (entries.containsKey(user)) {
941            return user;
942        }
943        String key = XmppStringUtils.parseBareJid(user);
944        return key.toLowerCase(Locale.US);
945    }
946
947    /**
948     * Changes the presence of available contacts offline by simulating an unavailable
949     * presence sent from the server. After a disconnection, every Presence is set
950     * to offline.
951     * @throws NotConnectedException 
952     */
953    private void setOfflinePresencesAndResetLoaded() {
954        Presence packetUnavailable;
955        for (String user : presenceMap.keySet()) {
956            Map<String, Presence> resources = presenceMap.get(user);
957            if (resources != null) {
958                for (String resource : resources.keySet()) {
959                    packetUnavailable = new Presence(Presence.Type.unavailable);
960                    packetUnavailable.setFrom(user + "/" + resource);
961                    try {
962                        presencePacketListener.processPacket(packetUnavailable);
963                    }
964                    catch (NotConnectedException e) {
965                        throw new IllegalStateException(
966                                        "presencePakcetListener should never throw a NotConnectedException when processPacket is called with a presence of type unavailable",
967                                        e);
968                    }
969                }
970            }
971        }
972        loaded = false;
973    }
974
975    /**
976     * Fires roster changed event to roster listeners indicating that the
977     * specified collections of contacts have been added, updated or deleted
978     * from the roster.
979     *
980     * @param addedEntries   the collection of address of the added contacts.
981     * @param updatedEntries the collection of address of the updated contacts.
982     * @param deletedEntries the collection of address of the deleted contacts.
983     */
984    private void fireRosterChangedEvent(final Collection<String> addedEntries, final Collection<String> updatedEntries,
985                    final Collection<String> deletedEntries) {
986        synchronized (rosterListenersAndEntriesLock) {
987            for (RosterListener listener : rosterListeners) {
988                if (!addedEntries.isEmpty()) {
989                    listener.entriesAdded(addedEntries);
990                }
991                if (!updatedEntries.isEmpty()) {
992                    listener.entriesUpdated(updatedEntries);
993                }
994                if (!deletedEntries.isEmpty()) {
995                    listener.entriesDeleted(deletedEntries);
996                }
997            }
998        }
999    }
1000
1001    /**
1002     * Fires roster presence changed event to roster listeners.
1003     *
1004     * @param presence the presence change.
1005     */
1006    private void fireRosterPresenceEvent(final Presence presence) {
1007        synchronized (rosterListenersAndEntriesLock) {
1008            for (RosterListener listener : rosterListeners) {
1009                listener.presenceChanged(presence);
1010            }
1011        }
1012    }
1013
1014    private void addUpdateEntry(Collection<String> addedEntries, Collection<String> updatedEntries,
1015                    Collection<String> unchangedEntries, RosterPacket.Item item, RosterEntry entry) {
1016        RosterEntry oldEntry;
1017        synchronized (rosterListenersAndEntriesLock) {
1018            oldEntry = entries.put(item.getUser(), entry);
1019        }
1020        if (oldEntry == null) {
1021            addedEntries.add(item.getUser());
1022        }
1023        else {
1024            RosterPacket.Item oldItem = RosterEntry.toRosterItem(oldEntry);
1025            if (!oldEntry.equalsDeep(entry) || !item.getGroupNames().equals(oldItem.getGroupNames())) {
1026                updatedEntries.add(item.getUser());
1027            } else {
1028                // Record the entry as unchanged, so that it doesn't end up as deleted entry
1029                unchangedEntries.add(item.getUser());
1030            }
1031        }
1032
1033        // Mark the entry as unfiled if it does not belong to any groups.
1034        if (item.getGroupNames().isEmpty()) {
1035            unfiledEntries.add(entry);
1036        }
1037        else {
1038            unfiledEntries.remove(entry);
1039        }
1040
1041        // Add the entry/user to the groups
1042        List<String> newGroupNames = new ArrayList<String>();
1043        for (String groupName : item.getGroupNames()) {
1044            // Add the group name to the list.
1045            newGroupNames.add(groupName);
1046
1047            // Add the entry to the group.
1048            RosterGroup group = getGroup(groupName);
1049            if (group == null) {
1050                group = createGroup(groupName);
1051                groups.put(groupName, group);
1052            }
1053            // Add the entry.
1054            group.addEntryLocal(entry);
1055        }
1056
1057        // Remove user from the remaining groups.
1058        List<String> oldGroupNames = new ArrayList<String>();
1059        for (RosterGroup group: getGroups()) {
1060            oldGroupNames.add(group.getName());
1061        }
1062        oldGroupNames.removeAll(newGroupNames);
1063
1064        for (String groupName : oldGroupNames) {
1065            RosterGroup group = getGroup(groupName);
1066            group.removeEntryLocal(entry);
1067            if (group.getEntryCount() == 0) {
1068                groups.remove(groupName);
1069            }
1070        }
1071    }
1072
1073    private void deleteEntry(Collection<String> deletedEntries, RosterEntry entry) {
1074        String user = entry.getUser();
1075        entries.remove(user);
1076        unfiledEntries.remove(entry);
1077        presenceMap.remove(XmppStringUtils.parseBareJid(user));
1078        deletedEntries.add(user);
1079
1080        for (Entry<String,RosterGroup> e: groups.entrySet()) {
1081            RosterGroup group = e.getValue();
1082            group.removeEntryLocal(entry);
1083            if (group.getEntryCount() == 0) {
1084                groups.remove(e.getKey());
1085            }
1086        }
1087    }
1088
1089
1090    /**
1091     * Removes all the groups with no entries.
1092     *
1093     * This is used by {@link RosterPushListener} and {@link RosterResultListener} to
1094     * cleanup groups after removing contacts.
1095     */
1096    private void removeEmptyGroups() {
1097        // We have to do this because RosterGroup.removeEntry removes the entry immediately
1098        // (locally) and the group could remain empty.
1099        // TODO Check the performance/logic for rosters with large number of groups
1100        for (RosterGroup group : getGroups()) {
1101            if (group.getEntryCount() == 0) {
1102                groups.remove(group.getName());
1103            }
1104        }
1105    }
1106
1107    /**
1108     * Ignore ItemTypes as of RFC 6121, 2.1.2.5.
1109     *
1110     * This is used by {@link RosterPushListener} and {@link RosterResultListener}.
1111     * */
1112    private static boolean hasValidSubscriptionType(RosterPacket.Item item) {
1113        switch (item.getItemType()) {
1114            case none:
1115            case from:
1116            case to:
1117            case both:
1118                return true;
1119            default:
1120                return false;
1121        }
1122    }
1123
1124    /**
1125     * Check if the server supports roster versioning.
1126     *
1127     * @return true if the server supports roster versioning, false otherwise.
1128     */
1129    public boolean isRosterVersioningSupported() {
1130        return connection().hasFeature(RosterVer.ELEMENT, RosterVer.NAMESPACE);
1131    }
1132
1133    /**
1134     * An enumeration for the subscription mode options.
1135     */
1136    public enum SubscriptionMode {
1137
1138        /**
1139         * Automatically accept all subscription and unsubscription requests. This is
1140         * the default mode and is suitable for simple client. More complex client will
1141         * likely wish to handle subscription requests manually.
1142         */
1143        accept_all,
1144
1145        /**
1146         * Automatically reject all subscription requests.
1147         */
1148        reject_all,
1149
1150        /**
1151         * Subscription requests are ignored, which means they must be manually
1152         * processed by registering a listener for presence packets and then looking
1153         * for any presence requests that have the type Presence.Type.SUBSCRIBE or
1154         * Presence.Type.UNSUBSCRIBE.
1155         */
1156        manual
1157    }
1158
1159    /**
1160     * Listens for all presence packets and processes them.
1161     */
1162    private class PresencePacketListener implements StanzaListener {
1163
1164        /**
1165         * Retrieve the user presences (a map from resource to {@link Presence}) for a given key (usually a JID without
1166         * a resource). If the {@link #presenceMap} does not contain already a user presence map, then it will be
1167         * created.
1168         * 
1169         * @param key the presence map key
1170         * @return the user presences
1171         */
1172        private Map<String, Presence> getUserPresences(String key) {
1173            Map<String, Presence> userPresences = presenceMap.get(key);
1174            if (userPresences == null) {
1175                userPresences = new ConcurrentHashMap<>();
1176                presenceMap.put(key, userPresences);
1177            }
1178            return userPresences;
1179        }
1180
1181        @Override
1182        public void processPacket(Stanza packet) throws NotConnectedException {
1183            final XMPPConnection connection = connection();
1184            Presence presence = (Presence) packet;
1185            String from = presence.getFrom();
1186            String key = getMapKey(from);
1187            Map<String, Presence> userPresences;
1188            Presence response = null;
1189
1190            // If an "available" presence, add it to the presence map. Each presence
1191            // map will hold for a particular user a map with the presence
1192            // packets saved for each resource.
1193            switch (presence.getType()) {
1194            case available:
1195                // Get the user presence map
1196                userPresences = getUserPresences(key);
1197                // See if an offline presence was being stored in the map. If so, remove
1198                // it since we now have an online presence.
1199                userPresences.remove("");
1200                // Add the new presence, using the resources as a key.
1201                userPresences.put(XmppStringUtils.parseResource(from), presence);
1202                // If the user is in the roster, fire an event.
1203                if (entries.containsKey(key)) {
1204                    fireRosterPresenceEvent(presence);
1205                }
1206                break;
1207            // If an "unavailable" packet.
1208            case unavailable:
1209                // If no resource, this is likely an offline presence as part of
1210                // a roster presence flood. In that case, we store it.
1211                if ("".equals(XmppStringUtils.parseResource(from))) {
1212                    // Get the user presence map
1213                    userPresences = getUserPresences(key);
1214                    userPresences.put("", presence);
1215                }
1216                // Otherwise, this is a normal offline presence.
1217                else if (presenceMap.get(key) != null) {
1218                    userPresences = presenceMap.get(key);
1219                    // Store the offline presence, as it may include extra information
1220                    // such as the user being on vacation.
1221                    userPresences.put(XmppStringUtils.parseResource(from), presence);
1222                }
1223                // If the user is in the roster, fire an event.
1224                if (entries.containsKey(key)) {
1225                    fireRosterPresenceEvent(presence);
1226                }
1227                break;
1228            case subscribe:
1229                switch (subscriptionMode) {
1230                case accept_all:
1231                    // Accept all subscription requests.
1232                    response = new Presence(Presence.Type.subscribed);
1233                    break;
1234                case reject_all:
1235                    // Reject all subscription requests.
1236                    response = new Presence(Presence.Type.unsubscribed);
1237                    break;
1238                case manual:
1239                default:
1240                    // Otherwise, in manual mode so ignore.
1241                    break;
1242                }
1243                if (response != null) {
1244                    response.setTo(presence.getFrom());
1245                    connection.sendStanza(response);
1246                }
1247                break;
1248            case unsubscribe:
1249                if (subscriptionMode != SubscriptionMode.manual) {
1250                    // Acknowledge and accept unsubscription notification so that the
1251                    // server will stop sending notifications saying that the contact
1252                    // has unsubscribed to our presence.
1253                    response = new Presence(Presence.Type.unsubscribed);
1254                    response.setTo(presence.getFrom());
1255                    connection.sendStanza(response);
1256                }
1257                // Otherwise, in manual mode so ignore.
1258                break;
1259            // Error presence packets from a bare JID mean we invalidate all existing
1260            // presence info for the user.
1261            case error:
1262                if (!"".equals(XmppStringUtils.parseResource(from))) {
1263                    break;
1264                }
1265                userPresences = getUserPresences(key);
1266                // Any other presence data is invalidated by the error packet.
1267                userPresences.clear();
1268
1269                // Set the new presence using the empty resource as a key.
1270                userPresences.put("", presence);
1271                // If the user is in the roster, fire an event.
1272                if (entries.containsKey(key)) {
1273                    fireRosterPresenceEvent(presence);
1274                }
1275                break;
1276            default:
1277                break;
1278            }
1279        }
1280    }
1281
1282    /**
1283     * Handles roster reults as described in RFC 6121 2.1.4
1284     */
1285    private class RosterResultListener implements StanzaListener {
1286
1287        @Override
1288        public void processPacket(Stanza packet) {
1289            final XMPPConnection connection = connection();
1290            LOGGER.fine("RosterResultListener received stanza");
1291            Collection<String> addedEntries = new ArrayList<String>();
1292            Collection<String> updatedEntries = new ArrayList<String>();
1293            Collection<String> deletedEntries = new ArrayList<String>();
1294            Collection<String> unchangedEntries = new ArrayList<String>();
1295
1296            if (packet instanceof RosterPacket) {
1297                // Non-empty roster result. This stanza contains all the roster elements.
1298                RosterPacket rosterPacket = (RosterPacket) packet;
1299
1300                // Ignore items without valid subscription type
1301                ArrayList<Item> validItems = new ArrayList<RosterPacket.Item>();
1302                for (RosterPacket.Item item : rosterPacket.getRosterItems()) {
1303                    if (hasValidSubscriptionType(item)) {
1304                        validItems.add(item);
1305                    }
1306                }
1307
1308                for (RosterPacket.Item item : validItems) {
1309                    RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
1310                            item.getItemType(), item.getItemStatus(), Roster.this, connection);
1311                    addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
1312                }
1313
1314                // Delete all entries which where not added or updated
1315                Set<String> toDelete = new HashSet<String>();
1316                for (RosterEntry entry : entries.values()) {
1317                    toDelete.add(entry.getUser());
1318                }
1319                toDelete.removeAll(addedEntries);
1320                toDelete.removeAll(updatedEntries);
1321                toDelete.removeAll(unchangedEntries);
1322                for (String user : toDelete) {
1323                    deleteEntry(deletedEntries, entries.get(user));
1324                }
1325
1326                if (rosterStore != null) {
1327                    String version = rosterPacket.getVersion();
1328                    rosterStore.resetEntries(validItems, version);
1329                }
1330
1331                removeEmptyGroups();
1332            }
1333            else {
1334                // Empty roster result as defined in RFC6121 2.6.3. An empty roster result basically
1335                // means that rosterver was used and the roster hasn't changed (much) since the
1336                // version we presented the server. So we simply load the roster from the store and
1337                // await possible further roster pushes.
1338                for (RosterPacket.Item item : rosterStore.getEntries()) {
1339                    RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
1340                            item.getItemType(), item.getItemStatus(), Roster.this, connection);
1341                    addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
1342                }
1343            }
1344
1345            loaded = true;
1346            synchronized (Roster.this) {
1347                Roster.this.notifyAll();
1348            }
1349            // Fire event for roster listeners.
1350            fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
1351
1352            // Call the roster loaded listeners after the roster events have been fired. This is
1353            // imporant because the user may call getEntriesAndAddListener() in onRosterLoaded(),
1354            // and if the order would be the other way around, the roster listener added by
1355            // getEntriesAndAddListener() would be invoked with information that was already
1356            // available at the time getEntriesAndAddListenr() was called.
1357            try {
1358                synchronized (rosterLoadedListeners) {
1359                    for (RosterLoadedListener rosterLoadedListener : rosterLoadedListeners) {
1360                        rosterLoadedListener.onRosterLoaded(Roster.this);
1361                    }
1362                }
1363            }
1364            catch (Exception e) {
1365                LOGGER.log(Level.WARNING, "RosterLoadedListener threw exception", e);
1366            }
1367        }
1368    }
1369
1370    /**
1371     * Listens for all roster pushes and processes them.
1372     */
1373    private class RosterPushListener extends AbstractIqRequestHandler {
1374
1375        private RosterPushListener() {
1376            super(RosterPacket.ELEMENT, RosterPacket.NAMESPACE, Type.set, Mode.sync);
1377        }
1378
1379        @Override
1380        public IQ handleIQRequest(IQ iqRequest) {
1381            final XMPPConnection connection = connection();
1382            RosterPacket rosterPacket = (RosterPacket) iqRequest;
1383
1384            // Roster push (RFC 6121, 2.1.6)
1385            // A roster push with a non-empty from not matching our address MUST be ignored
1386            String jid = XmppStringUtils.parseBareJid(connection.getUser());
1387            String from = rosterPacket.getFrom();
1388            if (from != null && !from.equals(jid)) {
1389                LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from
1390                                + "'");
1391                return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.service_unavailable));
1392            }
1393
1394            // A roster push must contain exactly one entry
1395            Collection<Item> items = rosterPacket.getRosterItems();
1396            if (items.size() != 1) {
1397                LOGGER.warning("Ignoring roster push with not exaclty one entry. size=" + items.size());
1398                return IQ.createErrorResponse(iqRequest, new XMPPError(Condition.bad_request));
1399            }
1400
1401            Collection<String> addedEntries = new ArrayList<String>();
1402            Collection<String> updatedEntries = new ArrayList<String>();
1403            Collection<String> deletedEntries = new ArrayList<String>();
1404            Collection<String> unchangedEntries = new ArrayList<String>();
1405
1406            // We assured above that the size of items is exaclty 1, therefore we are able to
1407            // safely retrieve this single item here.
1408            Item item = items.iterator().next();
1409            RosterEntry entry = new RosterEntry(item.getUser(), item.getName(),
1410                            item.getItemType(), item.getItemStatus(), Roster.this, connection);
1411            String version = rosterPacket.getVersion();
1412
1413            if (item.getItemType().equals(RosterPacket.ItemType.remove)) {
1414                deleteEntry(deletedEntries, entry);
1415                if (rosterStore != null) {
1416                    rosterStore.removeEntry(entry.getUser(), version);
1417                }
1418            }
1419            else if (hasValidSubscriptionType(item)) {
1420                addUpdateEntry(addedEntries, updatedEntries, unchangedEntries, item, entry);
1421                if (rosterStore != null) {
1422                    rosterStore.addEntry(item, version);
1423                }
1424            }
1425
1426            removeEmptyGroups();
1427
1428            // Fire event for roster listeners.
1429            fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries);
1430
1431            return IQ.createResultIQ(rosterPacket);
1432        }
1433    }
1434}