mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-08-23 01:10:28 +02:00
354 lines
16 KiB
Java
354 lines
16 KiB
Java
|
/**
|
||
|
* $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
|
||
|
* <a href="http://www.jabber.org/jeps/jep-0033.html">JEP-33: Extended Stanza Addressing</a>.
|
||
|
* 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 <tt>null</tt> if no TO
|
||
|
* list exists.
|
||
|
* @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC
|
||
|
* list exists.
|
||
|
* @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> 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 <tt>null</tt> if no TO
|
||
|
* list exists.
|
||
|
* @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC
|
||
|
* list exists.
|
||
|
* @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC
|
||
|
* list exists.
|
||
|
* @param replyTo address to which all replies are requested to be sent or <tt>null</tt>
|
||
|
* indicating that they can reply to any address.
|
||
|
* @param replyRoom JID of a MUC room to which responses should be sent or <tt>null</tt>
|
||
|
* 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 <thread/> 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
|
||
|
* <tt>null</tt> 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 <tt>null</tt>
|
||
|
* 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 <tt>null</tt> 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;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|