• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * Copyright 2003-2006 Jive Software.
7  *
8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 package org.jivesoftware.smackx;
22 
23 import org.jivesoftware.smack.Connection;
24 import org.jivesoftware.smack.XMPPException;
25 import org.jivesoftware.smack.packet.Message;
26 import org.jivesoftware.smack.packet.Packet;
27 import org.jivesoftware.smack.util.Cache;
28 import org.jivesoftware.smack.util.StringUtils;
29 import org.jivesoftware.smackx.packet.DiscoverInfo;
30 import org.jivesoftware.smackx.packet.DiscoverItems;
31 import org.jivesoftware.smackx.packet.MultipleAddresses;
32 
33 import java.util.ArrayList;
34 import java.util.Iterator;
35 import java.util.List;
36 
37 /**
38  * A MultipleRecipientManager allows to send packets to multiple recipients by making use of
39  * <a href="http://www.jabber.org/jeps/jep-0033.html">JEP-33: Extended Stanza Addressing</a>.
40  * It also allows to send replies to packets that were sent to multiple recipients.
41  *
42  * @author Gaston Dombiak
43  */
44 public class MultipleRecipientManager {
45 
46     /**
47      * Create a cache to hold the 100 most recently accessed elements for a period of
48      * 24 hours.
49      */
50     private static Cache<String, String> services = new Cache<String, String>(100, 24 * 60 * 60 * 1000);
51 
52     /**
53      * Sends the specified packet to the list of specified recipients using the
54      * specified connection. If the server has support for JEP-33 then only one
55      * packet is going to be sent to the server with the multiple recipient instructions.
56      * However, if JEP-33 is not supported by the server then the client is going to send
57      * the packet to each recipient.
58      *
59      * @param connection the connection to use to send the packet.
60      * @param packet     the packet to send to the list of recipients.
61      * @param to         the list of JIDs to include in the TO list or <tt>null</tt> if no TO
62      *                   list exists.
63      * @param cc         the list of JIDs to include in the CC list or <tt>null</tt> if no CC
64      *                   list exists.
65      * @param bcc        the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC
66      *                   list exists.
67      * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and
68      *                       some JEP-33 specific features were requested.
69      */
send(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc)70     public static void send(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc)
71             throws XMPPException {
72         send(connection, packet, to, cc, bcc, null, null, false);
73     }
74 
75     /**
76      * Sends the specified packet to the list of specified recipients using the
77      * specified connection. If the server has support for JEP-33 then only one
78      * packet is going to be sent to the server with the multiple recipient instructions.
79      * However, if JEP-33 is not supported by the server then the client is going to send
80      * the packet to each recipient.
81      *
82      * @param connection the connection to use to send the packet.
83      * @param packet     the packet to send to the list of recipients.
84      * @param to         the list of JIDs to include in the TO list or <tt>null</tt> if no TO
85      *                   list exists.
86      * @param cc         the list of JIDs to include in the CC list or <tt>null</tt> if no CC
87      *                   list exists.
88      * @param bcc        the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC
89      *                   list exists.
90      * @param replyTo    address to which all replies are requested to be sent or <tt>null</tt>
91      *                   indicating that they can reply to any address.
92      * @param replyRoom  JID of a MUC room to which responses should be sent or <tt>null</tt>
93      *                   indicating that they can reply to any address.
94      * @param noReply    true means that receivers should not reply to the message.
95      * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and
96      *                       some JEP-33 specific features were requested.
97      */
send(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc, String replyTo, String replyRoom, boolean noReply)98     public static void send(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc,
99             String replyTo, String replyRoom, boolean noReply) throws XMPPException {
100         String serviceAddress = getMultipleRecipienServiceAddress(connection);
101         if (serviceAddress != null) {
102             // Send packet to target users using multiple recipient service provided by the server
103             sendThroughService(connection, packet, to, cc, bcc, replyTo, replyRoom, noReply,
104                     serviceAddress);
105         }
106         else {
107             // Server does not support JEP-33 so try to send the packet to each recipient
108             if (noReply || (replyTo != null && replyTo.trim().length() > 0) ||
109                     (replyRoom != null && replyRoom.trim().length() > 0)) {
110                 // Some specified JEP-33 features were requested so throw an exception alerting
111                 // the user that this features are not available
112                 throw new XMPPException("Extended Stanza Addressing not supported by server");
113             }
114             // Send the packet to each individual recipient
115             sendToIndividualRecipients(connection, packet, to, cc, bcc);
116         }
117     }
118 
119     /**
120      * Sends a reply to a previously received packet that was sent to multiple recipients. Before
121      * attempting to send the reply message some checkings are performed. If any of those checkings
122      * fail then an XMPPException is going to be thrown with the specific error detail.
123      *
124      * @param connection the connection to use to send the reply.
125      * @param original   the previously received packet that was sent to multiple recipients.
126      * @param reply      the new message to send as a reply.
127      * @throws XMPPException if the original message was not sent to multiple recipients, or the
128      *                       original message cannot be replied or reply should be sent to a room.
129      */
reply(Connection connection, Message original, Message reply)130     public static void reply(Connection connection, Message original, Message reply)
131             throws XMPPException {
132         MultipleRecipientInfo info = getMultipleRecipientInfo(original);
133         if (info == null) {
134             throw new XMPPException("Original message does not contain multiple recipient info");
135         }
136         if (info.shouldNotReply()) {
137             throw new XMPPException("Original message should not be replied");
138         }
139         if (info.getReplyRoom() != null) {
140             throw new XMPPException("Reply should be sent through a room");
141         }
142         // Any <thread/> element from the initial message MUST be copied into the reply.
143         if (original.getThread() != null) {
144             reply.setThread(original.getThread());
145         }
146         MultipleAddresses.Address replyAddress = info.getReplyAddress();
147         if (replyAddress != null && replyAddress.getJid() != null) {
148             // Send reply to the reply_to address
149             reply.setTo(replyAddress.getJid());
150             connection.sendPacket(reply);
151         }
152         else {
153             // Send reply to multiple recipients
154             List<String> to = new ArrayList<String>();
155             List<String> cc = new ArrayList<String>();
156             for (Iterator<MultipleAddresses.Address> it = info.getTOAddresses().iterator(); it.hasNext();) {
157                 String jid = it.next().getJid();
158                 to.add(jid);
159             }
160             for (Iterator<MultipleAddresses.Address> it = info.getCCAddresses().iterator(); it.hasNext();) {
161                 String jid = it.next().getJid();
162                 cc.add(jid);
163             }
164             // Add original sender as a 'to' address (if not already present)
165             if (!to.contains(original.getFrom()) && !cc.contains(original.getFrom())) {
166                 to.add(original.getFrom());
167             }
168             // Remove the sender from the TO/CC list (try with bare JID too)
169             String from = connection.getUser();
170             if (!to.remove(from) && !cc.remove(from)) {
171                 String bareJID = StringUtils.parseBareAddress(from);
172                 to.remove(bareJID);
173                 cc.remove(bareJID);
174             }
175 
176             String serviceAddress = getMultipleRecipienServiceAddress(connection);
177             if (serviceAddress != null) {
178                 // Send packet to target users using multiple recipient service provided by the server
179                 sendThroughService(connection, reply, to, cc, null, null, null, false,
180                         serviceAddress);
181             }
182             else {
183                 // Server does not support JEP-33 so try to send the packet to each recipient
184                 sendToIndividualRecipients(connection, reply, to, cc, null);
185             }
186         }
187     }
188 
189     /**
190      * Returns the {@link MultipleRecipientInfo} contained in the specified packet or
191      * <tt>null</tt> if none was found. Only packets sent to multiple recipients will
192      * contain such information.
193      *
194      * @param packet the packet to check.
195      * @return the MultipleRecipientInfo contained in the specified packet or <tt>null</tt>
196      *         if none was found.
197      */
getMultipleRecipientInfo(Packet packet)198     public static MultipleRecipientInfo getMultipleRecipientInfo(Packet packet) {
199         MultipleAddresses extension = (MultipleAddresses) packet
200                 .getExtension("addresses", "http://jabber.org/protocol/address");
201         return extension == null ? null : new MultipleRecipientInfo(extension);
202     }
203 
sendToIndividualRecipients(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc)204     private static void sendToIndividualRecipients(Connection connection, Packet packet,
205             List<String> to, List<String> cc, List<String> bcc) {
206         if (to != null) {
207             for (Iterator<String> it = to.iterator(); it.hasNext();) {
208                 String jid = it.next();
209                 packet.setTo(jid);
210                 connection.sendPacket(new PacketCopy(packet.toXML()));
211             }
212         }
213         if (cc != null) {
214             for (Iterator<String> it = cc.iterator(); it.hasNext();) {
215                 String jid = it.next();
216                 packet.setTo(jid);
217                 connection.sendPacket(new PacketCopy(packet.toXML()));
218             }
219         }
220         if (bcc != null) {
221             for (Iterator<String> it = bcc.iterator(); it.hasNext();) {
222                 String jid = it.next();
223                 packet.setTo(jid);
224                 connection.sendPacket(new PacketCopy(packet.toXML()));
225             }
226         }
227     }
228 
sendThroughService(Connection connection, Packet packet, List<String> to, List<String> cc, List<String> bcc, String replyTo, String replyRoom, boolean noReply, String serviceAddress)229     private static void sendThroughService(Connection connection, Packet packet, List<String> to,
230             List<String> cc, List<String> bcc, String replyTo, String replyRoom, boolean noReply,
231             String serviceAddress) {
232         // Create multiple recipient extension
233         MultipleAddresses multipleAddresses = new MultipleAddresses();
234         if (to != null) {
235             for (Iterator<String> it = to.iterator(); it.hasNext();) {
236                 String jid = it.next();
237                 multipleAddresses.addAddress(MultipleAddresses.TO, jid, null, null, false, null);
238             }
239         }
240         if (cc != null) {
241             for (Iterator<String> it = cc.iterator(); it.hasNext();) {
242                 String jid = it.next();
243                 multipleAddresses.addAddress(MultipleAddresses.CC, jid, null, null, false, null);
244             }
245         }
246         if (bcc != null) {
247             for (Iterator<String> it = bcc.iterator(); it.hasNext();) {
248                 String jid = it.next();
249                 multipleAddresses.addAddress(MultipleAddresses.BCC, jid, null, null, false, null);
250             }
251         }
252         if (noReply) {
253             multipleAddresses.setNoReply();
254         }
255         else {
256             if (replyTo != null && replyTo.trim().length() > 0) {
257                 multipleAddresses
258                         .addAddress(MultipleAddresses.REPLY_TO, replyTo, null, null, false, null);
259             }
260             if (replyRoom != null && replyRoom.trim().length() > 0) {
261                 multipleAddresses.addAddress(MultipleAddresses.REPLY_ROOM, replyRoom, null, null,
262                         false, null);
263             }
264         }
265         // Set the multiple recipient service address as the target address
266         packet.setTo(serviceAddress);
267         // Add extension to packet
268         packet.addExtension(multipleAddresses);
269         // Send the packet
270         connection.sendPacket(packet);
271     }
272 
273     /**
274      * Returns the address of the multiple recipients service. To obtain such address service
275      * discovery is going to be used on the connected server and if none was found then another
276      * attempt will be tried on the server items. The discovered information is going to be
277      * cached for 24 hours.
278      *
279      * @param connection the connection to use for disco. The connected server is going to be
280      *                   queried.
281      * @return the address of the multiple recipients service or <tt>null</tt> if none was found.
282      */
getMultipleRecipienServiceAddress(Connection connection)283     private static String getMultipleRecipienServiceAddress(Connection connection) {
284         String serviceName = connection.getServiceName();
285         String serviceAddress = (String) services.get(serviceName);
286         if (serviceAddress == null) {
287             synchronized (services) {
288                 serviceAddress = (String) services.get(serviceName);
289                 if (serviceAddress == null) {
290 
291                     // Send the disco packet to the server itself
292                     try {
293                         DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection)
294                                 .discoverInfo(serviceName);
295                         // Check if the server supports JEP-33
296                         if (info.containsFeature("http://jabber.org/protocol/address")) {
297                             serviceAddress = serviceName;
298                         }
299                         else {
300                             // Get the disco items and send the disco packet to each server item
301                             DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection)
302                                     .discoverItems(serviceName);
303                             for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) {
304                                 DiscoverItems.Item item = it.next();
305                                 info = ServiceDiscoveryManager.getInstanceFor(connection)
306                                         .discoverInfo(item.getEntityID(), item.getNode());
307                                 if (info.containsFeature("http://jabber.org/protocol/address")) {
308                                     serviceAddress = serviceName;
309                                     break;
310                                 }
311                             }
312 
313                         }
314                         // Cache the discovered information
315                         services.put(serviceName, serviceAddress == null ? "" : serviceAddress);
316                     }
317                     catch (XMPPException e) {
318                         e.printStackTrace();
319                     }
320                 }
321             }
322         }
323 
324         return "".equals(serviceAddress) ? null : serviceAddress;
325     }
326 
327     /**
328      * Packet that holds the XML stanza to send. This class is useful when the same packet
329      * is needed to be sent to different recipients. Since using the same packet is not possible
330      * (i.e. cannot change the TO address of a queues packet to be sent) then this class was
331      * created to keep the XML stanza to send.
332      */
333     private static class PacketCopy extends Packet {
334 
335         private String text;
336 
337         /**
338          * Create a copy of a packet with the text to send. The passed text must be a valid text to
339          * send to the server, no validation will be done on the passed text.
340          *
341          * @param text the whole text of the packet to send
342          */
PacketCopy(String text)343         public PacketCopy(String text) {
344             this.text = text;
345         }
346 
toXML()347         public String toXML() {
348             return text;
349         }
350 
351     }
352 
353 }
354