org.jivesoftware.smackx.MultipleRecipientManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smack Show documentation
Show all versions of smack Show documentation
Smack is an Open Source XMPP (Jabber) client library for instant messaging and presence. This library provides the client side functionality as specified in the core XMPP specifications as related to the client side of said specifications.
The newest version!
/**
* $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.Connection;
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(Connection 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(Connection 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(Connection 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(Connection 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(Connection 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(Connection 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;
}
}
}