diff --git a/source/org/jivesoftware/smackx/MultipleRecipientInfo.java b/source/org/jivesoftware/smackx/MultipleRecipientInfo.java
new file mode 100644
index 000000000..6bc68f1f4
--- /dev/null
+++ b/source/org/jivesoftware/smackx/MultipleRecipientInfo.java
@@ -0,0 +1,98 @@
+/**
+ * $RCSfile$
+ * $Revision: $
+ * $Date: $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smackx.packet.MultipleAddresses;
+
+import java.util.List;
+
+/**
+ * MultipleRecipientInfo keeps information about the multiple recipients extension included
+ * in a received packet. Among the information we can find the list of TO and CC addresses.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleRecipientInfo {
+
+ MultipleAddresses extension;
+
+ MultipleRecipientInfo(MultipleAddresses extension) {
+ this.extension = extension;
+ }
+
+ /**
+ * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address}
+ * that were the primary recipients of the packet.
+ *
+ * @return list of primary recipients of the packet.
+ */
+ public List getTOAddresses() {
+ return extension.getAddressesOfType(MultipleAddresses.TO);
+ }
+
+ /**
+ * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address}
+ * that were the secondary recipients of the packet.
+ *
+ * @return list of secondary recipients of the packet.
+ */
+ public List getCCAddresses() {
+ return extension.getAddressesOfType(MultipleAddresses.CC);
+ }
+
+ /**
+ * Returns the JID of a MUC room to which responses should be sent or null if
+ * no specific address was provided. When no specific address was provided then the reply
+ * can be sent to any or all recipients. Otherwise, the user should join the specified room
+ * and send the reply to the room.
+ *
+ * @return the JID of a MUC room to which responses should be sent or null if
+ * no specific address was provided.
+ */
+ public String getReplyRoom() {
+ List replyRoom = extension.getAddressesOfType(MultipleAddresses.REPLY_ROOM);
+ return replyRoom.isEmpty() ? null : ((MultipleAddresses.Address) replyRoom.get(0)).getJid();
+ }
+
+ /**
+ * Returns true if the received packet should not be replied. Use
+ * {@link MultipleRecipientManager#reply(org.jivesoftware.smack.XMPPConnection, org.jivesoftware.smack.packet.Message, org.jivesoftware.smack.packet.Message)}
+ * to send replies.
+ *
+ * @return true if the received packet should not be replied.
+ */
+ public boolean shouldNotReply() {
+ return !extension.getAddressesOfType(MultipleAddresses.NO_REPLY).isEmpty();
+ }
+
+ /**
+ * Returns the address to which all replies are requested to be sent or null if
+ * no specific address was provided. When no specific address was provided then the reply
+ * can be sent to any or all recipients.
+ *
+ * @return the address to which all replies are requested to be sent or null if
+ * no specific address was provided.
+ */
+ public MultipleAddresses.Address getReplyAddress() {
+ List replyTo = extension.getAddressesOfType(MultipleAddresses.REPLY_TO);
+ return replyTo.isEmpty() ? null : (MultipleAddresses.Address) replyTo.get(0);
+ }
+}
diff --git a/source/org/jivesoftware/smackx/MultipleRecipientManager.java b/source/org/jivesoftware/smackx/MultipleRecipientManager.java
new file mode 100644
index 000000000..51a55a9d1
--- /dev/null
+++ b/source/org/jivesoftware/smackx/MultipleRecipientManager.java
@@ -0,0 +1,353 @@
+/**
+ * $RCSfile$
+ * $Revision: $
+ * $Date: $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.util.Cache;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+import org.jivesoftware.smackx.packet.MultipleAddresses;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A MultipleRecipientManager allows to send packets to multiple recipients by making use of
+ * JEP-33: Extended Stanza Addressing.
+ * It also allows to send replies to packets that were sent to multiple recipients.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleRecipientManager {
+
+ /**
+ * Create a cache to hold the 100 most recently accessed elements for a period of
+ * 24 hours.
+ */
+ private static Cache services = new Cache(100, 24 * 60 * 60 * 1000);
+
+ /**
+ * Sends the specified packet to the list of specified recipients using the
+ * specified connection. If the server has support for JEP-33 then only one
+ * packet is going to be sent to the server with the multiple recipient instructions.
+ * However, if JEP-33 is not supported by the server then the client is going to send
+ * the packet to each recipient.
+ *
+ * @param connection the connection to use to send the packet.
+ * @param packet the packet to send to the list of recipients.
+ * @param to the list of JIDs to include in the TO list or null if no TO
+ * list exists.
+ * @param cc the list of JIDs to include in the CC list or null if no CC
+ * list exists.
+ * @param bcc the list of JIDs to include in the BCC list or null if no BCC
+ * list exists.
+ * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and
+ * some JEP-33 specific features were requested.
+ */
+ public static void send(XMPPConnection connection, Packet packet, List to, List cc, List bcc)
+ throws XMPPException {
+ send(connection, packet, to, cc, bcc, null, null, false);
+ }
+
+ /**
+ * Sends the specified packet to the list of specified recipients using the
+ * specified connection. If the server has support for JEP-33 then only one
+ * packet is going to be sent to the server with the multiple recipient instructions.
+ * However, if JEP-33 is not supported by the server then the client is going to send
+ * the packet to each recipient.
+ *
+ * @param connection the connection to use to send the packet.
+ * @param packet the packet to send to the list of recipients.
+ * @param to the list of JIDs to include in the TO list or null if no TO
+ * list exists.
+ * @param cc the list of JIDs to include in the CC list or null if no CC
+ * list exists.
+ * @param bcc the list of JIDs to include in the BCC list or null if no BCC
+ * list exists.
+ * @param replyTo address to which all replies are requested to be sent or null
+ * indicating that they can reply to any address.
+ * @param replyRoom JID of a MUC room to which responses should be sent or null
+ * indicating that they can reply to any address.
+ * @param noReply true means that receivers should not reply to the message.
+ * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and
+ * some JEP-33 specific features were requested.
+ */
+ public static void send(XMPPConnection connection, Packet packet, List to, List cc, List bcc,
+ String replyTo, String replyRoom, boolean noReply) throws XMPPException {
+ String serviceAddress = getMultipleRecipienServiceAddress(connection);
+ if (serviceAddress != null) {
+ // Send packet to target users using multiple recipient service provided by the server
+ sendThroughService(connection, packet, to, cc, bcc, replyTo, replyRoom, noReply,
+ serviceAddress);
+ }
+ else {
+ // Server does not support JEP-33 so try to send the packet to each recipient
+ if (noReply || (replyTo != null && replyTo.trim().length() > 0) ||
+ (replyRoom != null && replyRoom.trim().length() > 0)) {
+ // Some specified JEP-33 features were requested so throw an exception alerting
+ // the user that this features are not available
+ throw new XMPPException("Extended Stanza Addressing not supported by server");
+ }
+ // Send the packet to each individual recipient
+ sendToIndividualRecipients(connection, packet, to, cc, bcc);
+ }
+ }
+
+ /**
+ * Sends a reply to a previously received packet that was sent to multiple recipients. Before
+ * attempting to send the reply message some checkings are performed. If any of those checkings
+ * fail then an XMPPException is going to be thrown with the specific error detail.
+ *
+ * @param connection the connection to use to send the reply.
+ * @param original the previously received packet that was sent to multiple recipients.
+ * @param reply the new message to send as a reply.
+ * @throws XMPPException if the original message was not sent to multiple recipients, or the
+ * original message cannot be replied or reply should be sent to a room.
+ */
+ public static void reply(XMPPConnection connection, Message original, Message reply)
+ throws XMPPException {
+ MultipleRecipientInfo info = getMultipleRecipientInfo(original);
+ if (info == null) {
+ throw new XMPPException("Original message does not contain multiple recipient info");
+ }
+ if (info.shouldNotReply()) {
+ throw new XMPPException("Original message should not be replied");
+ }
+ if (info.getReplyRoom() != null) {
+ throw new XMPPException("Reply should be sent through a room");
+ }
+ // Any element from the initial message MUST be copied into the reply.
+ if (original.getThread() != null) {
+ reply.setThread(original.getThread());
+ }
+ MultipleAddresses.Address replyAddress = info.getReplyAddress();
+ if (replyAddress != null && replyAddress.getJid() != null) {
+ // Send reply to the reply_to address
+ reply.setTo(replyAddress.getJid());
+ connection.sendPacket(reply);
+ }
+ else {
+ // Send reply to multiple recipients
+ List to = new ArrayList();
+ List cc = new ArrayList();
+ for (Iterator it = info.getTOAddresses().iterator(); it.hasNext();) {
+ String jid = ((MultipleAddresses.Address) it.next()).getJid();
+ to.add(jid);
+ }
+ for (Iterator it = info.getCCAddresses().iterator(); it.hasNext();) {
+ String jid = ((MultipleAddresses.Address) it.next()).getJid();
+ cc.add(jid);
+ }
+ // Add original sender as a 'to' address (if not already present)
+ if (!to.contains(original.getFrom()) && !cc.contains(original.getFrom())) {
+ to.add(original.getFrom());
+ }
+ // Remove the sender from the TO/CC list (try with bare JID too)
+ String from = connection.getUser();
+ if (!to.remove(from) && !cc.remove(from)) {
+ String bareJID = StringUtils.parseBareAddress(from);
+ to.remove(bareJID);
+ cc.remove(bareJID);
+ }
+
+ String serviceAddress = getMultipleRecipienServiceAddress(connection);
+ if (serviceAddress != null) {
+ // Send packet to target users using multiple recipient service provided by the server
+ sendThroughService(connection, reply, to, cc, null, null, null, false,
+ serviceAddress);
+ }
+ else {
+ // Server does not support JEP-33 so try to send the packet to each recipient
+ sendToIndividualRecipients(connection, reply, to, cc, null);
+ }
+ }
+ }
+
+ /**
+ * Returns the {@link MultipleRecipientInfo} contained in the specified packet or
+ * null if none was found. Only packets sent to multiple recipients will
+ * contain such information.
+ *
+ * @param packet the packet to check.
+ * @return the MultipleRecipientInfo contained in the specified packet or null
+ * if none was found.
+ */
+ public static MultipleRecipientInfo getMultipleRecipientInfo(Packet packet) {
+ MultipleAddresses extension = (MultipleAddresses) packet
+ .getExtension("addresses", "http://jabber.org/protocol/address");
+ return extension == null ? null : new MultipleRecipientInfo(extension);
+ }
+
+ private static void sendToIndividualRecipients(XMPPConnection connection, Packet packet,
+ List to, List cc, List bcc) {
+ if (to != null) {
+ for (Iterator it = to.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ packet.setTo(jid);
+ connection.sendPacket(new PacketCopy(packet.toXML()));
+ }
+ }
+ if (cc != null) {
+ for (Iterator it = cc.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ packet.setTo(jid);
+ connection.sendPacket(new PacketCopy(packet.toXML()));
+ }
+ }
+ if (bcc != null) {
+ for (Iterator it = bcc.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ packet.setTo(jid);
+ connection.sendPacket(new PacketCopy(packet.toXML()));
+ }
+ }
+ }
+
+ private static void sendThroughService(XMPPConnection connection, Packet packet, List to,
+ List cc, List bcc, String replyTo, String replyRoom, boolean noReply,
+ String serviceAddress) {
+ // Create multiple recipient extension
+ MultipleAddresses multipleAddresses = new MultipleAddresses();
+ if (to != null) {
+ for (Iterator it = to.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ multipleAddresses.addAddress(MultipleAddresses.TO, jid, null, null, false, null);
+ }
+ }
+ if (cc != null) {
+ for (Iterator it = cc.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ multipleAddresses.addAddress(MultipleAddresses.CC, jid, null, null, false, null);
+ }
+ }
+ if (bcc != null) {
+ for (Iterator it = bcc.iterator(); it.hasNext();) {
+ String jid = (String) it.next();
+ multipleAddresses.addAddress(MultipleAddresses.BCC, jid, null, null, false, null);
+ }
+ }
+ if (noReply) {
+ multipleAddresses.setNoReply();
+ }
+ else {
+ if (replyTo != null && replyTo.trim().length() > 0) {
+ multipleAddresses
+ .addAddress(MultipleAddresses.REPLY_TO, replyTo, null, null, false, null);
+ }
+ if (replyRoom != null && replyRoom.trim().length() > 0) {
+ multipleAddresses.addAddress(MultipleAddresses.REPLY_ROOM, replyRoom, null, null,
+ false, null);
+ }
+ }
+ // Set the multiple recipient service address as the target address
+ packet.setTo(serviceAddress);
+ // Add extension to packet
+ packet.addExtension(multipleAddresses);
+ // Send the packet
+ connection.sendPacket(packet);
+ }
+
+ /**
+ * Returns the address of the multiple recipients service. To obtain such address service
+ * discovery is going to be used on the connected server and if none was found then another
+ * attempt will be tried on the server items. The discovered information is going to be
+ * cached for 24 hours.
+ *
+ * @param connection the connection to use for disco. The connected server is going to be
+ * queried.
+ * @return the address of the multiple recipients service or null if none was found.
+ */
+ private static String getMultipleRecipienServiceAddress(XMPPConnection connection) {
+ String serviceName = connection.getServiceName();
+ String serviceAddress = (String) services.get(serviceName);
+ if (serviceAddress == null) {
+ synchronized (services) {
+ serviceAddress = (String) services.get(serviceName);
+ if (serviceAddress == null) {
+
+ // Send the disco packet to the server itself
+ try {
+ DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection)
+ .discoverInfo(serviceName);
+ // Check if the server supports JEP-33
+ if (info.containsFeature("http://jabber.org/protocol/address")) {
+ serviceAddress = serviceName;
+ }
+ else {
+ // Get the disco items and send the disco packet to each server item
+ DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection)
+ .discoverItems(serviceName);
+ for (Iterator it = items.getItems(); it.hasNext();) {
+ DiscoverItems.Item item = (DiscoverItems.Item) it.next();
+ info = ServiceDiscoveryManager.getInstanceFor(connection)
+ .discoverInfo(item.getEntityID(), item.getNode());
+ if (info.containsFeature("http://jabber.org/protocol/address")) {
+ serviceAddress = serviceName;
+ break;
+ }
+ }
+
+ }
+ // Cache the discovered information
+ services.put(serviceName, serviceAddress == null ? "" : serviceAddress);
+ }
+ catch (XMPPException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ return "".equals(serviceAddress) ? null : serviceAddress;
+ }
+
+ /**
+ * Packet that holds the XML stanza to send. This class is useful when the same packet
+ * is needed to be sent to different recipients. Since using the same packet is not possible
+ * (i.e. cannot change the TO address of a queues packet to be sent) then this class was
+ * created to keep the XML stanza to send.
+ */
+ private static class PacketCopy extends Packet {
+
+ private String text;
+
+ /**
+ * Create a copy of a packet with the text to send. The passed text must be a valid text to
+ * send to the server, no validation will be done on the passed text.
+ *
+ * @param text the whole text of the packet to send
+ */
+ public PacketCopy(String text) {
+ this.text = text;
+ }
+
+ public String toXML() {
+ return text;
+ }
+
+ }
+
+}
diff --git a/source/org/jivesoftware/smackx/packet/MultipleAddresses.java b/source/org/jivesoftware/smackx/packet/MultipleAddresses.java
new file mode 100644
index 000000000..6481127f2
--- /dev/null
+++ b/source/org/jivesoftware/smackx/packet/MultipleAddresses.java
@@ -0,0 +1,205 @@
+/**
+ * $RCSfile$
+ * $Revision: $
+ * $Date: $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.packet;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Packet extension that contains the list of addresses that a packet should be sent or was sent.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleAddresses implements PacketExtension {
+
+ public static final String BCC = "bcc";
+ public static final String CC = "cc";
+ public static final String NO_REPLY = "noreply";
+ public static final String REPLY_ROOM = "replyroom";
+ public static final String REPLY_TO = "replyto";
+ public static final String TO = "to";
+
+
+ private List addresses = new ArrayList();
+
+ /**
+ * Adds a new address to which the packet is going to be sent or was sent.
+ *
+ * @param type on of the static type (BCC, CC, NO_REPLY, REPLY_ROOM, etc.)
+ * @param jid the JID address of the recipient.
+ * @param node used to specify a sub-addressable unit at a particular JID, corresponding to
+ * a Service Discovery node.
+ * @param desc used to specify human-readable information for this address.
+ * @param delivered true when the packet was already delivered to this address.
+ * @param uri used to specify an external system address, such as a sip:, sips:, or im: URI.
+ */
+ public void addAddress(String type, String jid, String node, String desc, boolean delivered,
+ String uri) {
+ // Create a new address with the specificed configuration
+ Address address = new Address(type);
+ address.setJid(jid);
+ address.setNode(node);
+ address.setDescription(desc);
+ address.setDelivered(delivered);
+ address.setUri(uri);
+ // Add the new address to the list of multiple recipients
+ addresses.add(address);
+ }
+
+ /**
+ * Indicate that the packet being sent should not be replied.
+ */
+ public void setNoReply() {
+ // Create a new address with the specificed configuration
+ Address address = new Address(NO_REPLY);
+ // Add the new address to the list of multiple recipients
+ addresses.add(address);
+ }
+
+ /**
+ * Returns the list of addresses that matches the specified type. Examples of address
+ * type are: TO, CC, BCC, etc..
+ *
+ * @param type Examples of address type are: TO, CC, BCC, etc.
+ * @return the list of addresses that matches the specified type.
+ */
+ public List getAddressesOfType(String type) {
+ List answer = new ArrayList(addresses.size());
+ for (Iterator it = addresses.iterator(); it.hasNext();) {
+ Address address = (Address) it.next();
+ if (address.getType().equals(type)) {
+ answer.add(address);
+ }
+ }
+
+ return answer;
+ }
+
+ public String getElementName() {
+ return "addresses";
+ }
+
+ public String getNamespace() {
+ return "http://jabber.org/protocol/address";
+ }
+
+ public String toXML() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("<").append(getElementName());
+ buf.append(" xmlns=\"").append(getNamespace()).append("\">");
+ // Loop through all the addresses and append them to the string buffer
+ for (Iterator i = addresses.iterator(); i.hasNext();) {
+ Address address = (Address) i.next();
+ buf.append(address.toXML());
+ }
+ buf.append("").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+ public static class Address {
+
+ private String type;
+ private String jid;
+ private String node;
+ private String description;
+ private boolean delivered;
+ private String uri;
+
+ private Address(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getJid() {
+ return jid;
+ }
+
+ private void setJid(String jid) {
+ this.jid = jid;
+ }
+
+ public String getNode() {
+ return node;
+ }
+
+ private void setNode(String node) {
+ this.node = node;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ private void setDescription(String description) {
+ this.description = description;
+ }
+
+ public boolean isDelivered() {
+ return delivered;
+ }
+
+ private void setDelivered(boolean delivered) {
+ this.delivered = delivered;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ private void setUri(String uri) {
+ this.uri = uri;
+ }
+
+ private String toXML() {
+ StringBuffer buf = new StringBuffer();
+ buf.append("
0) {
+ buf.append(" desc=\"");
+ buf.append(description).append("\"");
+ }
+ if (delivered) {
+ buf.append(" delivered=\"true\"");
+ }
+ if (uri != null) {
+ buf.append(" uri=\"");
+ buf.append(uri).append("\"");
+ }
+ buf.append("/>");
+ return buf.toString();
+ }
+ }
+}
diff --git a/source/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java b/source/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java
new file mode 100644
index 000000000..b51e5e029
--- /dev/null
+++ b/source/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java
@@ -0,0 +1,67 @@
+/**
+ * $RCSfile$
+ * $Revision: $
+ * $Date: $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.packet.MultipleAddresses;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * The MultipleAddressesProvider parses {@link MultipleAddresses} packets.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleAddressesProvider implements PacketExtensionProvider {
+
+ /**
+ * Creates a new MultipleAddressesProvider.
+ * ProviderManager requires that every PacketExtensionProvider has a public, no-argument
+ * constructor.
+ */
+ public MultipleAddressesProvider() {
+ }
+
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ boolean done = false;
+ MultipleAddresses multipleAddresses = new MultipleAddresses();
+ while (!done) {
+ int eventType = parser.next();
+ if (eventType == XmlPullParser.START_TAG) {
+ if (parser.getName().equals("address")) {
+ String type = parser.getAttributeValue("", "type");
+ String jid = parser.getAttributeValue("", "jid");
+ String node = parser.getAttributeValue("", "node");
+ String desc = parser.getAttributeValue("", "desc");
+ boolean delivered = "true".equals(parser.getAttributeValue("", "delivered"));
+ String uri = parser.getAttributeValue("", "uri");
+ // Add the parsed address
+ multipleAddresses.addAddress(type, jid, node, desc, delivered, uri);
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if (parser.getName().equals(multipleAddresses.getElementName())) {
+ done = true;
+ }
+ }
+ }
+ return multipleAddresses;
+ }
+}
diff --git a/test/org/jivesoftware/smackx/MultipleRecipientManagerTest.java b/test/org/jivesoftware/smackx/MultipleRecipientManagerTest.java
new file mode 100644
index 000000000..30b72f99c
--- /dev/null
+++ b/test/org/jivesoftware/smackx/MultipleRecipientManagerTest.java
@@ -0,0 +1,254 @@
+/**
+ * $RCSfile$
+ * $Revision: $
+ * $Date: $
+ *
+ * Copyright 2003-2006 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smackx;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.MessageTypeFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.test.SmackTestCase;
+import org.jivesoftware.smackx.packet.MultipleAddresses;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests that JEP-33 support in Smack is correct.
+ *
+ * @author Gaston Dombiak
+ */
+public class MultipleRecipientManagerTest extends SmackTestCase {
+
+ public MultipleRecipientManagerTest(String arg0) {
+ super(arg0);
+ }
+
+ /**
+ * Ensures that sending and receiving of packets is ok.
+ */
+ public void testSending() throws XMPPException {
+
+ PacketCollector collector1 =
+ getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector2 =
+ getConnection(2).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector3 =
+ getConnection(3).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+
+ Message message = new Message();
+ message.setBody("Hola");
+ List to = Arrays.asList(new String[]{getBareJID(1)});
+ List cc = Arrays.asList(new String[]{getBareJID(2)});
+ List bcc = Arrays.asList(new String[]{getBareJID(3)});
+ MultipleRecipientManager.send(getConnection(0), message, to, cc, bcc);
+
+ Packet message1 = collector1.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 1 never received the message", message1);
+ MultipleRecipientInfo info1 = MultipleRecipientManager.getMultipleRecipientInfo(message1);
+ assertNotNull("Message 1 does not contain MultipleRecipientInfo", info1);
+ assertFalse("Message 1 should be 'replyable'", info1.shouldNotReply());
+ List addresses1 = info1.getTOAddresses();
+ assertEquals("Incorrect number of TO addresses", 1, addresses1.size());
+ String address1 = ((MultipleAddresses.Address) addresses1.get(0)).getJid();
+ assertEquals("Incorrect TO address", getBareJID(1), address1);
+ addresses1 = info1.getCCAddresses();
+ assertEquals("Incorrect number of CC addresses", 1, addresses1.size());
+ address1 = ((MultipleAddresses.Address) addresses1.get(0)).getJid();
+ assertEquals("Incorrect CC address", getBareJID(2), address1);
+
+ Packet message2 = collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 2 never received the message", message2);
+ MultipleRecipientInfo info2 = MultipleRecipientManager.getMultipleRecipientInfo(message2);
+ assertNotNull("Message 2 does not contain MultipleRecipientInfo", info2);
+ assertFalse("Message 2 should be 'replyable'", info2.shouldNotReply());
+ List addresses2 = info2.getTOAddresses();
+ assertEquals("Incorrect number of TO addresses", 1, addresses2.size());
+ String address2 = ((MultipleAddresses.Address) addresses2.get(0)).getJid();
+ assertEquals("Incorrect TO address", getBareJID(1), address2);
+ addresses2 = info2.getCCAddresses();
+ assertEquals("Incorrect number of CC addresses", 1, addresses2.size());
+ address2 = ((MultipleAddresses.Address) addresses2.get(0)).getJid();
+ assertEquals("Incorrect CC address", getBareJID(2), address2);
+
+ Packet message3 = collector3.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 3 never received the message", message3);
+ MultipleRecipientInfo info3 = MultipleRecipientManager.getMultipleRecipientInfo(message3);
+ assertNotNull("Message 3 does not contain MultipleRecipientInfo", info3);
+ assertFalse("Message 3 should be 'replyable'", info3.shouldNotReply());
+ List addresses3 = info3.getTOAddresses();
+ assertEquals("Incorrect number of TO addresses", 1, addresses3.size());
+ String address3 = ((MultipleAddresses.Address) addresses3.get(0)).getJid();
+ assertEquals("Incorrect TO address", getBareJID(1), address3);
+ addresses3 = info3.getCCAddresses();
+ assertEquals("Incorrect number of CC addresses", 1, addresses3.size());
+ address3 = ((MultipleAddresses.Address) addresses3.get(0)).getJid();
+ assertEquals("Incorrect CC address", getBareJID(2), address3);
+
+ collector1.cancel();
+ collector2.cancel();
+ collector3.cancel();
+ }
+
+ /**
+ * Ensures that replying to packets is ok.
+ */
+ public void testReplying() throws XMPPException {
+ PacketCollector collector0 =
+ getConnection(0).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector1 =
+ getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector2 =
+ getConnection(2).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector3 =
+ getConnection(3).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+
+ // Send the intial message with multiple recipients
+ Message message = new Message();
+ message.setBody("Hola");
+ List to = Arrays.asList(new String[]{getBareJID(1)});
+ List cc = Arrays.asList(new String[]{getBareJID(2)});
+ List bcc = Arrays.asList(new String[]{getBareJID(3)});
+ MultipleRecipientManager.send(getConnection(0), message, to, cc, bcc);
+
+ // Get the message and ensure it's ok
+ Message message1 =
+ (Message) collector1.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 1 never received the message", message1);
+ MultipleRecipientInfo info = MultipleRecipientManager.getMultipleRecipientInfo(message1);
+ assertNotNull("Message 1 does not contain MultipleRecipientInfo", info);
+ assertFalse("Message 1 should be 'replyable'", info.shouldNotReply());
+ assertEquals("Incorrect number of TO addresses", 1, info.getTOAddresses().size());
+ assertEquals("Incorrect number of CC addresses", 1, info.getCCAddresses().size());
+
+ // Prepare and send the reply
+ Message reply1 = new Message();
+ reply1.setBody("This is my reply");
+ MultipleRecipientManager.reply(getConnection(1), message1, reply1);
+
+ // Get the reply and ensure it's ok
+ reply1 = (Message) collector0.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 0 never received the reply", reply1);
+ info = MultipleRecipientManager.getMultipleRecipientInfo(reply1);
+ assertNotNull("Replied message does not contain MultipleRecipientInfo", info);
+ assertFalse("Replied message should be 'replyable'", info.shouldNotReply());
+ assertEquals("Incorrect number of TO addresses", 1, info.getTOAddresses().size());
+ assertEquals("Incorrect number of CC addresses", 1, info.getCCAddresses().size());
+
+ // Send a reply to the reply
+ Message reply2 = new Message();
+ reply2.setBody("This is my reply to your reply");
+ reply2.setFrom(getBareJID(0));
+ MultipleRecipientManager.reply(getConnection(0), reply1, reply2);
+
+ // Get the reply and ensure it's ok
+ reply2 = (Message) collector1.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 1 never received the reply", reply2);
+ info = MultipleRecipientManager.getMultipleRecipientInfo(reply2);
+ assertNotNull("Replied message does not contain MultipleRecipientInfo", info);
+ assertFalse("Replied message should be 'replyable'", info.shouldNotReply());
+ assertEquals("Incorrect number of TO addresses", 1, info.getTOAddresses().size());
+ assertEquals("Incorrect number of CC addresses", 1, info.getCCAddresses().size());
+
+ // Check that connection2 recevied 3 messages
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection2 didn't receive the 1 message", message1);
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection2 didn't receive the 2 message", message1);
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection2 didn't receive the 3 message", message1);
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNull("Connection2 received 4 messages", message1);
+
+ // Check that connection3 recevied only 1 message (was BCC in the first message)
+ message1 = (Message) collector3.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection3 didn't receive the 1 message", message1);
+ message1 = (Message) collector3.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNull("Connection2 received 2 messages", message1);
+
+ collector0.cancel();
+ collector1.cancel();
+ collector2.cancel();
+ collector3.cancel();
+ }
+
+ /**
+ * Ensures that replying is not allowed when disabled.
+ */
+ public void testNoReply() throws XMPPException {
+ PacketCollector collector1 =
+ getConnection(1).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector2 =
+ getConnection(2).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+ PacketCollector collector3 =
+ getConnection(3).createPacketCollector(new MessageTypeFilter(Message.Type.NORMAL));
+
+ // Send the intial message with multiple recipients
+ Message message = new Message();
+ message.setBody("Hola");
+ List to = Arrays.asList(new String[]{getBareJID(1)});
+ List cc = Arrays.asList(new String[]{getBareJID(2)});
+ List bcc = Arrays.asList(new String[]{getBareJID(3)});
+ MultipleRecipientManager.send(getConnection(0), message, to, cc, bcc, null, null, true);
+
+ // Get the message and ensure it's ok
+ Message message1 =
+ (Message) collector1.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection 1 never received the message", message1);
+ MultipleRecipientInfo info = MultipleRecipientManager.getMultipleRecipientInfo(message1);
+ assertNotNull("Message 1 does not contain MultipleRecipientInfo", info);
+ assertTrue("Message 1 should be not 'replyable'", info.shouldNotReply());
+ assertEquals("Incorrect number of TO addresses", 1, info.getTOAddresses().size());
+ assertEquals("Incorrect number of CC addresses", 1, info.getCCAddresses().size());
+
+ // Prepare and send the reply
+ Message reply1 = new Message();
+ reply1.setBody("This is my reply");
+ try {
+ MultipleRecipientManager.reply(getConnection(1), message1, reply1);
+ fail("It was possible to send a reply to a not replyable message");
+ }
+ catch (XMPPException e) {
+ // Exception was expected since replying was not allowed
+ }
+
+ // Check that connection2 recevied 1 messages
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection2 didn't receive the 1 message", message1);
+ message1 = (Message) collector2.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNull("Connection2 received 2 messages", message1);
+
+ // Check that connection3 recevied only 1 message (was BCC in the first message)
+ message1 = (Message) collector3.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNotNull("Connection3 didn't receive the 1 message", message1);
+ message1 = (Message) collector3.nextResult(SmackConfiguration.getPacketReplyTimeout());
+ assertNull("Connection2 received 2 messages", message1);
+
+ collector1.cancel();
+ collector2.cancel();
+ collector3.cancel();
+ }
+
+ protected int getMaxConnections() {
+ return 4;
+ }
+}