mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-10 17:49:38 +02:00
SMACK-272 Add support for XEP-0060 (pubsub)
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@11346 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
92ba2d7f33
commit
f7a1c750ad
73 changed files with 7214 additions and 0 deletions
525
source/org/jivesoftware/smackx/pubsub/Node.java
Normal file
525
source/org/jivesoftware/smackx/pubsub/Node.java
Normal file
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* Created on 2009-07-09
|
||||
*/
|
||||
package org.jivesoftware.smackx.pubsub;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jivesoftware.smack.PacketListener;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.filter.OrFilter;
|
||||
import org.jivesoftware.smack.filter.PacketFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.packet.IQ.Type;
|
||||
import org.jivesoftware.smackx.Form;
|
||||
import org.jivesoftware.smackx.packet.DelayInformation;
|
||||
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||
import org.jivesoftware.smackx.packet.Header;
|
||||
import org.jivesoftware.smackx.packet.HeadersExtension;
|
||||
import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener;
|
||||
import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
|
||||
import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
|
||||
import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend;
|
||||
import org.jivesoftware.smackx.pubsub.util.NodeUtils;
|
||||
|
||||
abstract public class Node
|
||||
{
|
||||
protected XMPPConnection con;
|
||||
protected String id;
|
||||
protected String to;
|
||||
|
||||
protected ConcurrentHashMap<ItemEventListener, PacketListener> itemEventToListenerMap = new ConcurrentHashMap<ItemEventListener, PacketListener>();
|
||||
protected ConcurrentHashMap<ItemDeleteListener, PacketListener> itemDeleteToListenerMap = new ConcurrentHashMap<ItemDeleteListener, PacketListener>();
|
||||
protected ConcurrentHashMap<NodeConfigListener, PacketListener> configEventToListenerMap = new ConcurrentHashMap<NodeConfigListener, PacketListener>();
|
||||
|
||||
/**
|
||||
* Construct a node associated to the supplied connection with the specified
|
||||
* node id.
|
||||
*
|
||||
* @param connection The connection the node is associated with
|
||||
* @param nodeName The node id
|
||||
*/
|
||||
Node(XMPPConnection connection, String nodeName)
|
||||
{
|
||||
con = connection;
|
||||
id = nodeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some XMPP servers may require a specific service to be addressed on the
|
||||
* server.
|
||||
*
|
||||
* For example, OpenFire requires the server to be prefixed by <b>pubsub</b>
|
||||
*/
|
||||
void setTo(String toAddress)
|
||||
{
|
||||
to = toAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the NodeId
|
||||
*
|
||||
* @return the node id
|
||||
*/
|
||||
public String getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
/**
|
||||
* Returns a configuration form, from which you can create an answer form to be submitted
|
||||
* via the {@link #sendConfigurationForm(Form)}.
|
||||
*
|
||||
* @return the configuration form
|
||||
*/
|
||||
public ConfigureForm getNodeConfiguration()
|
||||
throws XMPPException
|
||||
{
|
||||
Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER);
|
||||
return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the configuration with the contents of the new {@link Form}
|
||||
*
|
||||
* @param submitForm
|
||||
*/
|
||||
public void sendConfigurationForm(Form submitForm)
|
||||
throws XMPPException
|
||||
{
|
||||
PubSub packet = createPubsubPacket(Type.SET, new FormNode(FormNodeType.CONFIGURE_OWNER, getId(), submitForm), PubSubNamespace.OWNER);
|
||||
SyncPacketSend.getReply(con, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover node information in standard {@link DiscoverInfo} format.
|
||||
*
|
||||
* @return The discovery information about the node.
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public DiscoverInfo discoverInfo()
|
||||
throws XMPPException
|
||||
{
|
||||
DiscoverInfo info = new DiscoverInfo();
|
||||
info.setTo(to);
|
||||
info.setNode(getId());
|
||||
return (DiscoverInfo)SyncPacketSend.getReply(con, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscriptions currently associated with this node.
|
||||
*
|
||||
* @return List of {@link Subscription}
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public List<Subscription> getSubscriptions()
|
||||
throws XMPPException
|
||||
{
|
||||
PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId()));
|
||||
SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS);
|
||||
return subElem.getSubscriptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* The user subscribes to the node using the supplied jid. The
|
||||
* bare jid portion of this one must match the jid for the connection.
|
||||
*
|
||||
* Please note that the {@link Subscription.State} should be checked
|
||||
* on return since more actions may be required by the caller.
|
||||
* {@link Subscription.State#pending} - The owner must approve the subscription
|
||||
* request before messages will be received.
|
||||
* {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
|
||||
* the caller must configure the subscription before messages will be received. If it is false
|
||||
* the caller can configure it but is not required to do so.
|
||||
* @param jid The jid to subscribe as.
|
||||
* @return The subscription
|
||||
* @exception XMPPException
|
||||
*/
|
||||
public Subscription subscribe(String jid)
|
||||
throws XMPPException
|
||||
{
|
||||
PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new SubscribeExtension(jid, getId()));
|
||||
return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user subscribes to the node using the supplied jid and subscription
|
||||
* options. The bare jid portion of this one must match the jid for the
|
||||
* connection.
|
||||
*
|
||||
* Please note that the {@link Subscription.State} should be checked
|
||||
* on return since more actions may be required by the caller.
|
||||
* {@link Subscription.State#pending} - The owner must approve the subscription
|
||||
* request before messages will be received.
|
||||
* {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
|
||||
* the caller must configure the subscription before messages will be received. If it is false
|
||||
* the caller can configure it but is not required to do so.
|
||||
* @param jid The jid to subscribe as.
|
||||
* @return The subscription
|
||||
* @exception XMPPException
|
||||
*/
|
||||
public Subscription subscribe(String jid, SubscribeForm subForm)
|
||||
throws XMPPException
|
||||
{
|
||||
PubSub request = createPubsubPacket(Type.SET, new SubscribeExtension(jid, getId()));
|
||||
request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
|
||||
PubSub reply = (PubSub)PubSubManager.sendPubsubPacket(con, jid, Type.SET, request);
|
||||
return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the subscription related to the specified JID. This will only
|
||||
* work if there is only 1 subscription. If there are multiple subscriptions,
|
||||
* use {@link #unsubscribe(String, String)}.
|
||||
*
|
||||
* @param jid The JID used to subscribe to the node
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public void unsubscribe(String jid)
|
||||
throws XMPPException
|
||||
{
|
||||
unsubscribe(jid, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specific subscription related to the specified JID.
|
||||
*
|
||||
* @param jid The JID used to subscribe to the node
|
||||
* @param subscriptionId The id of the subscription being removed
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public void unsubscribe(String jid, String subscriptionId)
|
||||
throws XMPPException
|
||||
{
|
||||
sendPubsubPacket(Type.SET, new UnsubscribeExtension(jid, getId(), subscriptionId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted
|
||||
* via the {@link #sendConfigurationForm(Form)}.
|
||||
*
|
||||
* @return A subscription options form
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public SubscribeForm getSubscriptionOptions(String jid)
|
||||
throws XMPPException
|
||||
{
|
||||
return getSubscriptionOptions(jid, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the options for configuring the specified subscription.
|
||||
*
|
||||
* @param jid JID the subscription is registered under
|
||||
* @param subscriptionId The subscription id
|
||||
*
|
||||
* @return The subscription option form
|
||||
*
|
||||
* @throws XMPPException
|
||||
*/
|
||||
public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId)
|
||||
throws XMPPException
|
||||
{
|
||||
PubSub packet = (PubSub)sendPubsubPacket(Type.GET, new OptionsExtension(jid, getId(), subscriptionId));
|
||||
FormNode ext = (FormNode)packet.getExtension(PubSubElementType.OPTIONS);
|
||||
return new SubscribeForm(ext.getForm());
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener for item publication events. This
|
||||
* listener will get called whenever an item is published to
|
||||
* this node.
|
||||
*
|
||||
* @param listener The handler for the event
|
||||
*/
|
||||
public void addItemEventListener(ItemEventListener listener)
|
||||
{
|
||||
PacketListener conListener = new ItemEventTranslator(listener);
|
||||
itemEventToListenerMap.put(listener, conListener);
|
||||
con.addPacketListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener for publication events.
|
||||
*
|
||||
* @param listener The handler to unregister
|
||||
*/
|
||||
public void removeItemEventListener(ItemEventListener listener)
|
||||
{
|
||||
PacketListener conListener = itemEventToListenerMap.remove(listener);
|
||||
|
||||
if (conListener != null)
|
||||
con.removePacketListener(conListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a listener for configuration events. This listener
|
||||
* will get called whenever the node's configuration changes.
|
||||
*
|
||||
* @param listener The handler for the event
|
||||
*/
|
||||
public void addConfigurationListener(NodeConfigListener listener)
|
||||
{
|
||||
PacketListener conListener = new NodeConfigTranslator(listener);
|
||||
configEventToListenerMap.put(listener, conListener);
|
||||
con.addPacketListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener for configuration events.
|
||||
*
|
||||
* @param listener The handler to unregister
|
||||
*/
|
||||
public void removeConfigurationListener(NodeConfigListener listener)
|
||||
{
|
||||
PacketListener conListener = configEventToListenerMap .remove(listener);
|
||||
|
||||
if (conListener != null)
|
||||
con.removePacketListener(conListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an listener for item delete events. This listener
|
||||
* gets called whenever an item is deleted from the node.
|
||||
*
|
||||
* @param listener The handler for the event
|
||||
*/
|
||||
public void addItemDeleteListener(ItemDeleteListener listener)
|
||||
{
|
||||
PacketListener delListener = new ItemDeleteTranslator(listener);
|
||||
itemDeleteToListenerMap.put(listener, delListener);
|
||||
EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
|
||||
EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
|
||||
|
||||
con.addPacketListener(delListener, new OrFilter(deleteItem, purge));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a listener for item delete events.
|
||||
*
|
||||
* @param listener The handler to unregister
|
||||
*/
|
||||
public void removeItemDeleteListener(ItemDeleteListener listener)
|
||||
{
|
||||
PacketListener conListener = itemDeleteToListenerMap .remove(listener);
|
||||
|
||||
if (conListener != null)
|
||||
con.removePacketListener(conListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return super.toString() + " " + getClass().getName() + " id: " + id;
|
||||
}
|
||||
|
||||
protected PubSub createPubsubPacket(Type type, PacketExtension ext)
|
||||
{
|
||||
return createPubsubPacket(type, ext, null);
|
||||
}
|
||||
|
||||
protected PubSub createPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns)
|
||||
{
|
||||
return PubSubManager.createPubsubPacket(to, type, ext, ns);
|
||||
}
|
||||
|
||||
protected Packet sendPubsubPacket(Type type, NodeExtension ext)
|
||||
throws XMPPException
|
||||
{
|
||||
return PubSubManager.sendPubsubPacket(con, to, type, ext);
|
||||
}
|
||||
|
||||
protected Packet sendPubsubPacket(Type type, NodeExtension ext, PubSubNamespace ns)
|
||||
throws XMPPException
|
||||
{
|
||||
return PubSubManager.sendPubsubPacket(con, to, type, ext, ns);
|
||||
}
|
||||
|
||||
|
||||
private static List<String> getSubscriptionIds(Packet packet)
|
||||
{
|
||||
HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim");
|
||||
List<String> values = null;
|
||||
|
||||
if (headers != null)
|
||||
{
|
||||
values = new ArrayList<String>(headers.getHeaders().size());
|
||||
|
||||
for (Header header : headers.getHeaders())
|
||||
{
|
||||
values.add(header.getValue());
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class translates low level item publication events into api level objects for
|
||||
* user consumption.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
public class ItemEventTranslator implements PacketListener
|
||||
{
|
||||
private ItemEventListener listener;
|
||||
|
||||
public ItemEventTranslator(ItemEventListener eventListener)
|
||||
{
|
||||
listener = eventListener;
|
||||
}
|
||||
|
||||
public void processPacket(Packet packet)
|
||||
{
|
||||
EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
|
||||
ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
|
||||
DelayInformation delay = (DelayInformation)packet.getExtension("delay", "urn:xmpp:delay");
|
||||
|
||||
// If there was no delay based on XEP-0203, then try XEP-0091 for backward compatibility
|
||||
if (delay == null)
|
||||
{
|
||||
delay = (DelayInformation)packet.getExtension("x", "jabber:x:delay");
|
||||
}
|
||||
ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List<Item>)itemsElem.getItems(), getSubscriptionIds(packet), (delay == null ? null : delay.getStamp()));
|
||||
listener.handlePublishedItems(eventItems);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class translates low level item deletion events into api level objects for
|
||||
* user consumption.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
public class ItemDeleteTranslator implements PacketListener
|
||||
{
|
||||
private ItemDeleteListener listener;
|
||||
|
||||
public ItemDeleteTranslator(ItemDeleteListener eventListener)
|
||||
{
|
||||
listener = eventListener;
|
||||
}
|
||||
|
||||
public void processPacket(Packet packet)
|
||||
{
|
||||
EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
|
||||
|
||||
List<PacketExtension> extList = event.getExtensions();
|
||||
|
||||
if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName()))
|
||||
{
|
||||
listener.handlePurge();
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
|
||||
Collection<? extends PacketExtension> pubItems = itemsElem.getItems();
|
||||
Iterator<RetractItem> it = (Iterator<RetractItem>)pubItems.iterator();
|
||||
List<String> items = new ArrayList<String>(pubItems.size());
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
RetractItem item = it.next();
|
||||
items.add(item.getId());
|
||||
}
|
||||
|
||||
ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet));
|
||||
listener.handleDeletedItems(eventItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class translates low level node configuration events into api level objects for
|
||||
* user consumption.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
public class NodeConfigTranslator implements PacketListener
|
||||
{
|
||||
private NodeConfigListener listener;
|
||||
|
||||
public NodeConfigTranslator(NodeConfigListener eventListener)
|
||||
{
|
||||
listener = eventListener;
|
||||
}
|
||||
|
||||
public void processPacket(Packet packet)
|
||||
{
|
||||
EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
|
||||
ConfigurationEvent config = (ConfigurationEvent)event.getEvent();
|
||||
|
||||
listener.handleNodeConfiguration(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter for {@link PacketListener} to filter out events not specific to the
|
||||
* event type expected for this node.
|
||||
*
|
||||
* @author Robin Collier
|
||||
*/
|
||||
class EventContentFilter implements PacketFilter
|
||||
{
|
||||
private String firstElement;
|
||||
private String secondElement;
|
||||
|
||||
EventContentFilter(String elementName)
|
||||
{
|
||||
firstElement = elementName;
|
||||
}
|
||||
|
||||
EventContentFilter(String firstLevelEelement, String secondLevelElement)
|
||||
{
|
||||
firstElement = firstLevelEelement;
|
||||
secondElement = secondLevelElement;
|
||||
}
|
||||
|
||||
public boolean accept(Packet packet)
|
||||
{
|
||||
if (!(packet instanceof Message))
|
||||
return false;
|
||||
|
||||
EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
|
||||
|
||||
if (event == null)
|
||||
return false;
|
||||
|
||||
NodeExtension embedEvent = event.getEvent();
|
||||
|
||||
if (embedEvent == null)
|
||||
return false;
|
||||
|
||||
if (embedEvent.getElementName().equals(firstElement))
|
||||
{
|
||||
if (!embedEvent.getNode().equals(getId()))
|
||||
return false;
|
||||
|
||||
if (secondElement == null)
|
||||
return true;
|
||||
|
||||
if (embedEvent instanceof EmbeddedPacketExtension)
|
||||
{
|
||||
List<PacketExtension> secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions();
|
||||
|
||||
if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue