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 */
017package org.jivesoftware.smackx.xroster;
018
019import java.lang.ref.WeakReference;
020import java.util.Collections;
021import java.util.HashSet;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Set;
025import java.util.WeakHashMap;
026
027import org.jivesoftware.smack.SmackException.NotConnectedException;
028import org.jivesoftware.smack.StanzaListener;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.filter.StanzaExtensionFilter;
031import org.jivesoftware.smack.filter.StanzaFilter;
032import org.jivesoftware.smack.packet.Message;
033import org.jivesoftware.smack.packet.Stanza;
034import org.jivesoftware.smack.roster.Roster;
035import org.jivesoftware.smack.roster.RosterEntry;
036import org.jivesoftware.smack.roster.RosterGroup;
037
038import org.jivesoftware.smackx.xroster.packet.RosterExchange;
039
040import org.jxmpp.jid.Jid;
041
042/**
043 *
044 * Manages Roster exchanges. A RosterExchangeManager provides a high level access to send 
045 * rosters, roster groups and roster entries to XMPP clients. It also provides an easy way
046 * to hook up custom logic when entries are received from another XMPP client through 
047 * RosterExchangeListeners.
048 *
049 * @author Gaston Dombiak
050 */
051public class RosterExchangeManager {
052
053    public final static String NAMESPACE = "jabber:x:roster";
054    public final static String ELEMENT = "x";
055
056    private final static Map<XMPPConnection, RosterExchangeManager> INSTANCES = new WeakHashMap<>();
057
058    private final static StanzaFilter PACKET_FILTER = new StanzaExtensionFilter(ELEMENT, NAMESPACE);
059
060    private final Set<RosterExchangeListener> rosterExchangeListeners = Collections.synchronizedSet(new HashSet<RosterExchangeListener>());
061
062    private final WeakReference<XMPPConnection> weakRefConnection;
063    private final StanzaListener packetListener;
064
065    public synchronized static RosterExchangeManager getInstanceFor(XMPPConnection connection) {
066        RosterExchangeManager rosterExchangeManager = INSTANCES.get(connection);
067        if (rosterExchangeManager == null) {
068            rosterExchangeManager = new RosterExchangeManager(connection);
069            INSTANCES.put(connection, rosterExchangeManager);
070        }
071        return rosterExchangeManager;
072    }
073
074    /**
075     * Creates a new roster exchange manager.
076     *
077     * @param connection an XMPPConnection which is used to send and receive messages.
078     */
079    public RosterExchangeManager(XMPPConnection connection) {
080        weakRefConnection = new WeakReference<>(connection);
081        // Listens for all roster exchange packets and fire the roster exchange listeners.
082        packetListener = new StanzaListener() {
083            @Override
084            public void processStanza(Stanza packet) {
085                Message message = (Message) packet;
086                RosterExchange rosterExchange = message.getExtension(ELEMENT, NAMESPACE);
087                // Fire event for roster exchange listeners
088                fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries());
089            }
090        };
091        connection.addAsyncStanzaListener(packetListener, PACKET_FILTER);
092    }
093
094    /**
095     * Adds a listener to roster exchanges. The listener will be fired anytime roster entries
096     * are received from remote XMPP clients.
097     *
098     * @param rosterExchangeListener a roster exchange listener.
099     */
100    public void addRosterListener(RosterExchangeListener rosterExchangeListener) {
101        rosterExchangeListeners.add(rosterExchangeListener);
102    }
103
104    /**
105     * Removes a listener from roster exchanges. The listener will be fired anytime roster 
106     * entries are received from remote XMPP clients.
107     *
108     * @param rosterExchangeListener a roster exchange listener..
109     */
110    public void removeRosterListener(RosterExchangeListener rosterExchangeListener) {
111        rosterExchangeListeners.remove(rosterExchangeListener);
112    }
113
114    /**
115     * Sends a roster to userID. All the entries of the roster will be sent to the
116     * target user.
117     * 
118     * @param roster the roster to send
119     * @param targetUserID the user that will receive the roster entries
120     * @throws NotConnectedException 
121     * @throws InterruptedException 
122     */
123    public void send(Roster roster, Jid targetUserID) throws NotConnectedException, InterruptedException {
124        // Create a new message to send the roster
125        Message msg = new Message(targetUserID);
126        // Create a RosterExchange Package and add it to the message
127        RosterExchange rosterExchange = new RosterExchange(roster);
128        msg.addExtension(rosterExchange);
129
130        XMPPConnection connection = weakRefConnection.get();
131        // Send the message that contains the roster
132        connection.sendStanza(msg);
133    }
134
135    /**
136     * Sends a roster entry to userID.
137     * 
138     * @param rosterEntry the roster entry to send
139     * @param targetUserID the user that will receive the roster entries
140     * @throws NotConnectedException 
141     * @throws InterruptedException 
142     */
143    public void send(RosterEntry rosterEntry, Jid targetUserID) throws NotConnectedException, InterruptedException {
144        // Create a new message to send the roster
145        Message msg = new Message(targetUserID);
146        // Create a RosterExchange Package and add it to the message
147        RosterExchange rosterExchange = new RosterExchange();
148        rosterExchange.addRosterEntry(rosterEntry);
149        msg.addExtension(rosterExchange);
150
151        XMPPConnection connection = weakRefConnection.get();
152        // Send the message that contains the roster
153        connection.sendStanza(msg);
154    }
155
156    /**
157     * Sends a roster group to userID. All the entries of the group will be sent to the 
158     * target user.
159     * 
160     * @param rosterGroup the roster group to send
161     * @param targetUserID the user that will receive the roster entries
162     * @throws NotConnectedException 
163     * @throws InterruptedException 
164     */
165    public void send(RosterGroup rosterGroup, Jid targetUserID) throws NotConnectedException, InterruptedException {
166        // Create a new message to send the roster
167        Message msg = new Message(targetUserID);
168        // Create a RosterExchange Package and add it to the message
169        RosterExchange rosterExchange = new RosterExchange();
170        for (RosterEntry entry : rosterGroup.getEntries()) {
171            rosterExchange.addRosterEntry(entry);
172        }
173        msg.addExtension(rosterExchange);
174
175        XMPPConnection connection = weakRefConnection.get();
176        // Send the message that contains the roster
177        connection.sendStanza(msg);
178    }
179
180    /**
181     * Fires roster exchange listeners.
182     */
183    private void fireRosterExchangeListeners(Jid from, Iterator<RemoteRosterEntry> remoteRosterEntries) {
184        RosterExchangeListener[] listeners;
185        synchronized (rosterExchangeListeners) {
186            listeners = new RosterExchangeListener[rosterExchangeListeners.size()];
187            rosterExchangeListeners.toArray(listeners);
188        }
189        for (RosterExchangeListener listener : listeners) {
190            listener.entriesReceived(from, remoteRosterEntries);
191        }
192    }
193}