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