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(""); + 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; + } +}