PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/servers/jain-slee/resources/xmpp/library/src/main/java/org/jivesoftware/smackx/MultipleRecipientManager.java

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