Improve PubSub API

Use Manager pattern for PubSubManager.

Also improve the API of ServiceDiscoverManager.
This commit is contained in:
Florian Schmaus 2015-05-12 17:56:06 +02:00
parent 9e351f0535
commit 001e824fb9
16 changed files with 368 additions and 140 deletions

View File

@ -53,7 +53,7 @@ Create a node with default configuration and then configure it:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Create the node
LeafNode leaf = mgr.createNode("testNode");
@ -71,7 +71,7 @@ Create and configure a node:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Create the node
ConfigureForm form = new ConfigureForm(FormType.submit);
@ -108,7 +108,7 @@ In this example we publish an item to a node that does not take payload:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -124,7 +124,7 @@ In this example we publish an item to a node that does take payload:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -167,7 +167,7 @@ subscribe for messages.
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -198,7 +198,7 @@ subscribe for item deletion messages.
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -230,7 +230,7 @@ subscribe for node configuration messages.
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
Node node = mgr.getNode("testNode");
@ -286,7 +286,7 @@ In this example we can see how to retrieve the existing items from a node:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -298,7 +298,7 @@ In this example we can see how to retrieve the last N existing items:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -310,7 +310,7 @@ In this example we can see how to retrieve the specified existing items:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the node
LeafNode node = mgr.getNode("testNode");
@ -341,7 +341,7 @@ In this example we can see how to get pubsub capabilities:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the pubsub features that are supported
DiscoverInfo supportedFeatures = mgr.getSupportedFeatures();
@ -351,7 +351,7 @@ In this example we can see how to get pubsub subscriptions for all nodes:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get all the subscriptions in the pubsub service
List&ltSubscription;> subscriptions = mgr.getSubscriptions();
@ -362,7 +362,7 @@ on the pubsub service:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
// Get the affiliations for the users bare JID
List&ltAffiliation;> affiliations = mgr.getAffiliations();
@ -372,7 +372,7 @@ In this example we can see how to get information about the node:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
Node node = mgr.getNode("testNode");
// Get the node information
@ -383,7 +383,7 @@ In this example we can see how to discover the node items:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
Node node = mgr.getNode("testNode");
// Discover the node items
@ -394,7 +394,7 @@ In this example we can see how to get node subscriptions:
```
// Create a pubsub manager using an existing XMPPConnection
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = PubSubManager.getInstanceFor(con);
Node node = mgr.getNode("testNode");
// Discover the node subscriptions

View File

@ -283,11 +283,7 @@ public class MultipleRecipientManager {
*/
private static DomainBareJid getMultipleRecipienServiceAddress(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
List<DomainBareJid> services = sdm.findServices(MultipleAddresses.NAMESPACE, true, true);
if (services.size() > 0) {
return services.get(0);
}
return null;
return sdm.findService(MultipleAddresses.NAMESPACE, true);
}
/**

View File

@ -684,7 +684,7 @@ public final class ServiceDiscoveryManager extends Manager {
* Create a cache to hold the 25 most recently lookup services for a given feature for a period
* of 24 hours.
*/
private Cache<String, List<DomainBareJid>> services = new ExpirationCache<>(25,
private Cache<String, List<DiscoverInfo>> services = new ExpirationCache<>(25,
24 * 60 * 60 * 1000);
/**
@ -699,17 +699,17 @@ public final class ServiceDiscoveryManager extends Manager {
* @throws NotConnectedException
* @throws InterruptedException
*/
public List<DomainBareJid> findServices(String feature, boolean stopOnFirst, boolean useCache)
public List<DiscoverInfo> findServicesDiscoverInfo(String feature, boolean stopOnFirst, boolean useCache)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
List<DomainBareJid> serviceAddresses = null;
List<DiscoverInfo> serviceDiscoInfo = null;
DomainBareJid serviceName = connection().getServiceName();
if (useCache) {
serviceAddresses = services.get(feature);
if (serviceAddresses != null) {
return serviceAddresses;
serviceDiscoInfo = services.get(feature);
if (serviceDiscoInfo != null) {
return serviceDiscoInfo;
}
}
serviceAddresses = new LinkedList<>();
serviceDiscoInfo = new LinkedList<>();
// Send the disco packet to the server itself
DiscoverInfo info;
try {
@ -717,17 +717,17 @@ public final class ServiceDiscoveryManager extends Manager {
} catch (XMPPErrorException e) {
// Be extra robust here: Return the empty linked list and log this situation
LOGGER.log(Level.WARNING, "Could not discover information about service", e);
return serviceAddresses;
return serviceDiscoInfo;
}
// Check if the server supports XEP-33
// Check if the server supports the feature
if (info.containsFeature(feature)) {
serviceAddresses.add(serviceName);
serviceDiscoInfo.add(info);
if (stopOnFirst) {
if (useCache) {
// Cache the discovered information
services.put(feature, serviceAddresses);
services.put(feature, serviceDiscoInfo);
}
return serviceAddresses;
return serviceDiscoInfo;
}
}
DiscoverItems items;
@ -736,7 +736,7 @@ public final class ServiceDiscoveryManager extends Manager {
items = discoverItems(serviceName);
} catch(XMPPErrorException e) {
LOGGER.log(Level.WARNING, "Could not discover items about service", e);
return serviceAddresses;
return serviceDiscoInfo;
}
for (DiscoverItems.Item item : items.getItems()) {
try {
@ -752,7 +752,8 @@ public final class ServiceDiscoveryManager extends Manager {
continue;
}
if (info.containsFeature(feature)) {
serviceAddresses.add(item.getEntityID().asDomainBareJid());
serviceDiscoInfo.add(info);
//serviceAddresses.add(item.getEntityID().asDomainBareJid());
if (stopOnFirst) {
break;
}
@ -760,9 +761,54 @@ public final class ServiceDiscoveryManager extends Manager {
}
if (useCache) {
// Cache the discovered information
services.put(feature, serviceAddresses);
services.put(feature, serviceDiscoInfo);
}
return serviceAddresses;
return serviceDiscoInfo;
}
/**
* Find all services under the users service that provide a given feature.
*
* @param feature the feature to search for
* @param stopOnFirst if true, stop searching after the first service was found
* @param useCache if true, query a cache first to avoid network I/O
* @return a possible empty list of services providing the given feature
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
*/
public List<DomainBareJid> findServices(String feature, boolean stopOnFirst, boolean useCache) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
List<DiscoverInfo> services = findServicesDiscoverInfo(feature, stopOnFirst, useCache);
List<DomainBareJid> res = new ArrayList<>(services.size());
for (DiscoverInfo info : services) {
res.add(info.getFrom().asDomainBareJid());
}
return res;
}
public DomainBareJid findService(String feature, boolean useCache, String category, String type)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
List<DiscoverInfo> services = findServicesDiscoverInfo(feature, true, useCache);
if (services.isEmpty()) {
return null;
}
DiscoverInfo info = services.get(0);
if (category != null && type != null) {
if (!info.hasIdentity(category, type)) {
return null;
}
}
else if (category != null || type != null) {
throw new IllegalArgumentException("Must specify either both, category and type, or none");
}
return info.getFrom().asDomainBareJid();
}
public DomainBareJid findService(String feature, boolean useCache) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
return findService(feature, useCache, null, null);
}
/**

View File

@ -16,13 +16,11 @@
*/
package org.jivesoftware.smackx.pubsub;
import org.jivesoftware.smack.XMPPConnection;
public class CollectionNode extends Node
{
CollectionNode(XMPPConnection connection, String nodeId)
CollectionNode(PubSubManager pubSubManager, String nodeId)
{
super(connection, nodeId);
super(pubSubManager, nodeId);
}
}

View File

@ -22,7 +22,6 @@ import java.util.List;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.ExtensionElement;
@ -39,9 +38,9 @@ import org.jivesoftware.smackx.pubsub.packet.PubSub;
*/
public class LeafNode extends Node
{
LeafNode(XMPPConnection connection, String nodeName)
LeafNode(PubSubManager pubSubManager, String nodeId)
{
super(connection, nodeName);
super(pubSubManager, nodeId);
}
/**
@ -57,9 +56,9 @@ public class LeafNode extends Node
public DiscoverItems discoverItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
DiscoverItems items = new DiscoverItems();
items.setTo(to);
items.setTo(pubSubManager.getServiceJid());
items.setNode(getId());
return (DiscoverItems) con.createPacketCollectorAndSend(items).nextResultOrThrow();
return pubSubManager.getConnection().createPacketCollectorAndSend(items).nextResultOrThrow();
}
/**
@ -193,7 +192,7 @@ public class LeafNode extends Node
private <T extends Item> List<T> getItems(PubSub request,
List<ExtensionElement> returnedExtensions) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
PubSub result = con.createPacketCollectorAndSend(request).nextResultOrThrow();
PubSub result = pubSubManager.getConnection().createPacketCollectorAndSend(request).nextResultOrThrow();
ItemsExtension itemsElem = result.getExtension(PubSubElementType.ITEMS);
if (returnedExtensions != null) {
returnedExtensions.addAll(result.getExtensions());
@ -219,7 +218,7 @@ public class LeafNode extends Node
{
PubSub packet = createPubsubPacket(Type.set, new NodeExtension(PubSubElementType.PUBLISH, getId()));
con.sendStanza(packet);
pubSubManager.getConnection().sendStanza(packet);
}
/**
@ -266,7 +265,7 @@ public class LeafNode extends Node
{
PubSub packet = createPubsubPacket(Type.set, new PublishItem<T>(getId(), items));
con.sendStanza(packet);
pubSubManager.getConnection().sendStanza(packet);
}
/**
@ -290,7 +289,7 @@ public class LeafNode extends Node
{
PubSub packet = createPubsubPacket(Type.set, new NodeExtension(PubSubElementType.PUBLISH, getId()));
con.createPacketCollectorAndSend(packet).nextResultOrThrow();
pubSubManager.getConnection().createPacketCollectorAndSend(packet).nextResultOrThrow();
}
/**
@ -347,7 +346,7 @@ public class LeafNode extends Node
{
PubSub packet = createPubsubPacket(Type.set, new PublishItem<T>(getId(), items));
con.createPacketCollectorAndSend(packet).nextResultOrThrow();
pubSubManager.getConnection().createPacketCollectorAndSend(packet).nextResultOrThrow();
}
/**
@ -364,7 +363,7 @@ public class LeafNode extends Node
{
PubSub request = createPubsubPacket(Type.set, new NodeExtension(PubSubElementType.PURGE_OWNER, getId()), PubSubElementType.PURGE_OWNER.getNamespace());
con.createPacketCollectorAndSend(request).nextResultOrThrow();
pubSubManager.getConnection().createPacketCollectorAndSend(request).nextResultOrThrow();
}
/**
@ -401,6 +400,6 @@ public class LeafNode extends Node
items.add(new Item(id));
}
PubSub request = createPubsubPacket(Type.set, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items));
con.createPacketCollectorAndSend(request).nextResultOrThrow();
pubSubManager.getConnection().createPacketCollectorAndSend(request).nextResultOrThrow();
}
}

View File

@ -24,7 +24,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
@ -43,13 +42,11 @@ import org.jivesoftware.smackx.pubsub.util.NodeUtils;
import org.jivesoftware.smackx.shim.packet.Header;
import org.jivesoftware.smackx.shim.packet.HeadersExtension;
import org.jivesoftware.smackx.xdata.Form;
import org.jxmpp.jid.Jid;
abstract public class Node
{
protected XMPPConnection con;
protected String id;
protected Jid to;
protected final PubSubManager pubSubManager;
protected final String id;
protected ConcurrentHashMap<ItemEventListener<Item>, StanzaListener> itemEventToListenerMap = new ConcurrentHashMap<ItemEventListener<Item>, StanzaListener>();
protected ConcurrentHashMap<ItemDeleteListener, StanzaListener> itemDeleteToListenerMap = new ConcurrentHashMap<ItemDeleteListener, StanzaListener>();
@ -62,21 +59,10 @@ abstract public class Node
* @param connection The connection the node is associated with
* @param nodeName The node id
*/
Node(XMPPConnection connection, String nodeName)
Node(PubSubManager pubSubManager, String nodeId)
{
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(Jid toAddress)
{
to = toAddress;
this.pubSubManager = pubSubManager;
id = nodeId;
}
/**
@ -119,7 +105,7 @@ abstract public class Node
{
PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER,
getId(), submitForm), PubSubNamespace.OWNER);
con.createPacketCollectorAndSend(packet).nextResultOrThrow();
pubSubManager.getConnection().createPacketCollectorAndSend(packet).nextResultOrThrow();
}
/**
@ -134,9 +120,9 @@ abstract public class Node
public DiscoverInfo discoverInfo() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
DiscoverInfo info = new DiscoverInfo();
info.setTo(to);
info.setTo(pubSubManager.getServiceJid());
info.setNode(getId());
return (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow();
return pubSubManager.getConnection().createPacketCollectorAndSend(info).nextResultOrThrow();
}
/**
@ -336,7 +322,7 @@ abstract public class Node
PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
// CHECKSTYLE:ON
request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
PubSub reply = PubSubManager.sendPubsubPacket(con, request);
PubSub reply = sendPubsubPacket(request);
return reply.getExtension(PubSubElementType.SUBSCRIPTION);
}
@ -420,7 +406,7 @@ abstract public class Node
{
StanzaListener conListener = new ItemEventTranslator(listener);
itemEventToListenerMap.put(listener, conListener);
con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
pubSubManager.getConnection().addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
}
/**
@ -433,7 +419,7 @@ abstract public class Node
StanzaListener conListener = itemEventToListenerMap.remove(listener);
if (conListener != null)
con.removeSyncStanzaListener(conListener);
pubSubManager.getConnection().removeSyncStanzaListener(conListener);
}
/**
@ -446,7 +432,7 @@ abstract public class Node
{
StanzaListener conListener = new NodeConfigTranslator(listener);
configEventToListenerMap.put(listener, conListener);
con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
pubSubManager.getConnection().addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
}
/**
@ -459,7 +445,7 @@ abstract public class Node
StanzaListener conListener = configEventToListenerMap .remove(listener);
if (conListener != null)
con.removeSyncStanzaListener(conListener);
pubSubManager.getConnection().removeSyncStanzaListener(conListener);
}
/**
@ -475,7 +461,7 @@ abstract public class Node
EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
con.addSyncStanzaListener(delListener, new OrFilter(deleteItem, purge));
pubSubManager.getConnection().addSyncStanzaListener(delListener, new OrFilter(deleteItem, purge));
}
/**
@ -488,7 +474,7 @@ abstract public class Node
StanzaListener conListener = itemDeleteToListenerMap .remove(listener);
if (conListener != null)
con.removeSyncStanzaListener(conListener);
pubSubManager.getConnection().removeSyncStanzaListener(conListener);
}
@Override
@ -504,12 +490,12 @@ abstract public class Node
protected PubSub createPubsubPacket(Type type, ExtensionElement ext, PubSubNamespace ns)
{
return PubSub.createPubsubPacket(to, type, ext, ns);
return PubSub.createPubsubPacket(pubSubManager.getServiceJid(), type, ext, ns);
}
protected PubSub sendPubsubPacket(PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
return PubSubManager.sendPubsubPacket(con, packet);
return pubSubManager.sendPubsubPacket(packet);
}

View File

@ -17,16 +17,22 @@
package org.jivesoftware.smackx.pubsub;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.EmptyResultIQ;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.ExtensionElement;
@ -52,24 +58,73 @@ import org.jxmpp.stringprep.XmppStringprepException;
*
* @author Robin Collier
*/
final public class PubSubManager
{
private XMPPConnection con;
private DomainBareJid to;
private Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>();
public final class PubSubManager extends Manager {
/**
* Create a pubsub manager associated to the specified connection. Defaults the service
* name to <i>pubsub</i>
*
* @param connection The XMPP connection
* @throws XmppStringprepException
*/
public PubSubManager(XMPPConnection connection) throws XmppStringprepException
{
con = connection;
to = JidCreate.domainBareFrom("pubsub." + connection.getServiceName());
}
private static final Logger LOGGER = Logger.getLogger(PubSubManager.class.getName());
private static final Map<XMPPConnection, Map<DomainBareJid, PubSubManager>> INSTANCES = new WeakHashMap<>();
/**
* The JID of the PubSub service this manager manages.
*/
private final DomainBareJid pubSubService;
/**
* A map of node IDs to Nodes, used to cache those Nodes. This does only cache the type of Node,
* i.e. {@link CollectionNode} or {@link LeafNode}.
*/
private final Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>();
/**
* Get a PubSub manager for the default PubSub service of the connection.
*
* @param connection
* @return the default PubSub manager.
*/
public static PubSubManager getInstance(XMPPConnection connection) {
DomainBareJid pubSubService = null;
if (connection.isAuthenticated()) {
try {
pubSubService = getPubSubService(connection);
}
catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could not determine PubSub service", e);
}
catch (InterruptedException e) {
LOGGER.log(Level.FINE, "Interupted while trying to determine PubSub service", e);
}
}
if (pubSubService == null) {
try {
// Perform an educated guess about what the PubSub service's domain bare JID may be
pubSubService = JidCreate.domainBareFrom("pubsub." + connection.getServiceName());
}
catch (XmppStringprepException e) {
throw new RuntimeException(e);
}
}
return getInstance(connection, pubSubService);
}
/**
* Get the PubSub manager for the given connection and PubSub service.
*
* @param connection the XMPP connection.
* @param pubSubService the PubSub service.
* @return a PubSub manager for the connection and service.
*/
public static synchronized PubSubManager getInstance(XMPPConnection connection, DomainBareJid pubSubService) {
Map<DomainBareJid, PubSubManager> managers = INSTANCES.get(connection);
if (managers == null) {
managers = new HashMap<>();
INSTANCES.put(connection, managers);
}
PubSubManager pubSubManager = managers.get(pubSubService);
if (pubSubManager == null) {
pubSubManager = new PubSubManager(connection, pubSubService);
managers.put(pubSubService, pubSubManager);
}
return pubSubManager;
}
/**
* Create a pubsub manager associated to the specified connection where
@ -78,10 +133,10 @@ final public class PubSubManager
* @param connection The XMPP connection
* @param toAddress The pubsub specific to address (required for some servers)
*/
public PubSubManager(XMPPConnection connection, DomainBareJid toAddress)
PubSubManager(XMPPConnection connection, DomainBareJid toAddress)
{
con = connection;
to = toAddress;
super(connection);
pubSubService = toAddress;
}
/**
@ -98,8 +153,7 @@ final public class PubSubManager
PubSub reply = sendPubsubPacket(Type.set, new NodeExtension(PubSubElementType.CREATE), null);
NodeExtension elem = reply.getExtension("create", PubSubNamespace.BASIC.getXmlns());
LeafNode newNode = new LeafNode(con, elem.getNode());
newNode.setTo(to);
LeafNode newNode = new LeafNode(this, elem.getNode());
nodeMap.put(newNode.getId(), newNode);
return newNode;
@ -108,7 +162,7 @@ final public class PubSubManager
/**
* Creates a node with default configuration.
*
* @param id The id of the node, which must be unique within the
* @param nodeId The id of the node, which must be unique within the
* pubsub service
* @return The node that was created
* @throws XMPPErrorException
@ -116,9 +170,9 @@ final public class PubSubManager
* @throws NotConnectedException
* @throws InterruptedException
*/
public LeafNode createNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
public LeafNode createNode(String nodeId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
return (LeafNode)createNode(id, null);
return (LeafNode) createNode(nodeId, null);
}
/**
@ -126,7 +180,7 @@ final public class PubSubManager
*
* Note: This is the only way to create a collection node.
*
* @param name The name of the node, which must be unique within the
* @param nodeId The name of the node, which must be unique within the
* pubsub service
* @param config The configuration for the node
* @return The node that was created
@ -135,9 +189,9 @@ final public class PubSubManager
* @throws NotConnectedException
* @throws InterruptedException
*/
public Node createNode(String name, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
public Node createNode(String nodeId, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
PubSub request = PubSub.createPubsubPacket(to, Type.set, new NodeExtension(PubSubElementType.CREATE, name), null);
PubSub request = PubSub.createPubsubPacket(pubSubService, Type.set, new NodeExtension(PubSubElementType.CREATE, nodeId), null);
boolean isLeafNode = true;
if (config != null)
@ -151,9 +205,8 @@ final public class PubSubManager
// Errors will cause exceptions in getReply, so it only returns
// on success.
sendPubsubPacket(con, request);
Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name);
newNode.setTo(to);
sendPubsubPacket(request);
Node newNode = isLeafNode ? new LeafNode(this, nodeId) : new CollectionNode(this, nodeId);
nodeMap.put(newNode.getId(), newNode);
return newNode;
@ -177,16 +230,16 @@ final public class PubSubManager
if (node == null)
{
DiscoverInfo info = new DiscoverInfo();
info.setTo(to);
info.setTo(pubSubService);
info.setNode(id);
DiscoverInfo infoReply = (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow();
DiscoverInfo infoReply = connection().createPacketCollectorAndSend(info).nextResultOrThrow();
if (infoReply.hasIdentity(PubSub.ELEMENT, "leaf")) {
node = new LeafNode(con, id);
node = new LeafNode(this, id);
}
else if (infoReply.hasIdentity(PubSub.ELEMENT, "collection")) {
node = new CollectionNode(con, id);
node = new CollectionNode(this, id);
}
else {
// XEP-60 5.3 states that
@ -194,12 +247,11 @@ final public class PubSubManager
// If this is not the case, then we are dealing with an PubSub implementation that doesn't follow the specification.
throw new AssertionError(
"PubSub service '"
+ to
+ pubSubService
+ "' returned disco info result for node '"
+ id
+ "', but it did not contain an Identity of type 'leaf' or 'collection' (and category 'pubsub'), which is not allowed according to XEP-60 5.3.");
}
node.setTo(to);
nodeMap.put(id, node);
}
@SuppressWarnings("unchecked")
@ -229,8 +281,8 @@ final public class PubSubManager
if (nodeId != null)
items.setNode(nodeId);
items.setTo(to);
DiscoverItems nodeItems = (DiscoverItems) con.createPacketCollectorAndSend(items).nextResultOrThrow();
items.setTo(pubSubService);
DiscoverItems nodeItems = connection().createPacketCollectorAndSend(items).nextResultOrThrow();
return nodeItems;
}
@ -299,6 +351,15 @@ final public class PubSubManager
return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT);
}
/**
* Get the JID of the PubSub service managed by this manager.
*
* @return the JID of the PubSub service.
*/
public DomainBareJid getServiceJid() {
return pubSubService;
}
/**
* Gets the supported features of the servers pubsub implementation
* as a standard {@link DiscoverInfo} instance.
@ -311,33 +372,92 @@ final public class PubSubManager
*/
public DiscoverInfo getSupportedFeatures() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con);
return mgr.discoverInfo(to);
ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(connection());
return mgr.discoverInfo(pubSubService);
}
/**
* Check if it is possible to create PubSub nodes on this service. It could be possible that the
* PubSub service allows only certain XMPP entities (clients) to create nodes and publish items
* to them.
* <p>
* Note that since XEP-60 does not provide an API to determine if an XMPP entity is allowed to
* create nodes, therefore this method creates an instant node calling {@link #createNode()} to
* determine if it is possible to create nodes.
* </p>
*
* @return <code>true</code> if it is possible to create nodes, <code>false</code> otherwise.
* @throws NoResponseException
* @throws NotConnectedException
* @throws InterruptedException
* @throws XMPPErrorException
*/
public boolean canCreateNodesAndPublishItems() throws NoResponseException, NotConnectedException, InterruptedException, XMPPErrorException {
LeafNode leafNode = null;
try {
leafNode = createNode();
}
catch (XMPPErrorException e) {
if (e.getXMPPError().getCondition() == XMPPError.Condition.forbidden) {
return false;
}
throw e;
} finally {
if (leafNode != null) {
deleteNode(leafNode.getId());
}
}
return true;
}
private PubSub sendPubsubPacket(Type type, ExtensionElement ext, PubSubNamespace ns)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return sendPubsubPacket(con, to, type, Collections.singletonList(ext), ns);
return sendPubsubPacket(pubSubService, type, Collections.singletonList(ext), ns);
}
static PubSub sendPubsubPacket(XMPPConnection con, Jid to, Type type, List<ExtensionElement> extList, PubSubNamespace ns) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
XMPPConnection getConnection() {
return connection();
}
PubSub sendPubsubPacket(Jid to, Type type, List<ExtensionElement> extList, PubSubNamespace ns)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
// CHECKSTYLE:OFF
PubSub pubSub = new PubSub(to, type, ns);
for (ExtensionElement pe : extList) {
pubSub.addExtension(pe);
}
// CHECKSTYLE:ON
return sendPubsubPacket(con ,pubSub);
return sendPubsubPacket(pubSub);
}
static PubSub sendPubsubPacket(XMPPConnection con, PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
{
IQ resultIQ = con.createPacketCollectorAndSend(packet).nextResultOrThrow();
PubSub sendPubsubPacket(PubSub packet) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException {
IQ resultIQ = connection().createPacketCollectorAndSend(packet).nextResultOrThrow();
if (resultIQ instanceof EmptyResultIQ) {
return null;
}
return (PubSub) resultIQ;
}
/**
* Get the "default" PubSub service for a given XMPP connection. The default PubSub service is
* simply an arbitrary XMPP service with the PubSub feature and an identity of category "pubsub"
* and type "service".
*
* @param connection
* @return the default PubSub service or <code>null</code>.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @see <a href="http://xmpp.org/extensions/xep-0060.html#entity-features">XEP-60 § 5.1 Discover
* Features</a>
*/
public static DomainBareJid getPubSubService(XMPPConnection connection)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException {
return ServiceDiscoveryManager.getInstanceFor(connection).findService(PubSub.NAMESPACE,
true, "pubsub", "service");
}
}

View File

@ -16,6 +16,6 @@
*/
/**
* TODO describe me.
* Listeners for Publish-Subscribe (XEP-60) events.
*/
package org.jivesoftware.smackx.pubsub.listener;

View File

@ -16,6 +16,6 @@
*/
/**
* TODO describe me.
* Smack's API for XEP-60: Publish-Subscribe.
*/
package org.jivesoftware.smackx.pubsub;

View File

@ -16,6 +16,6 @@
*/
/**
* TODO describe me.
* Stanzas and extension elements for Publish-Subscribe (XEP-60).
*/
package org.jivesoftware.smackx.pubsub.packet;

View File

@ -16,6 +16,6 @@
*/
/**
* TODO describe me.
* Providers for Publish-Subscribe (XEP-60).
*/
package org.jivesoftware.smackx.pubsub.provider;

View File

@ -16,6 +16,6 @@
*/
/**
* TODO describe me.
* Utilities for Publish-Subscribe (XEP-60).
*/
package org.jivesoftware.smackx.pubsub.util;

View File

@ -54,7 +54,7 @@ public class ConfigureFormTest
public void getConfigFormWithInsufficientPriviliges() throws XMPPException, SmackException, IOException, InterruptedException
{
ThreadedDummyConnection con = ThreadedDummyConnection.newInstance();
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = new PubSubManager(con, PubSubManagerTest.DUMMY_PUBSUB_SERVICE);
DiscoverInfo info = new DiscoverInfo();
Identity ident = new Identity("pubsub", null, "leaf");
info.addIdentity(ident);
@ -81,7 +81,7 @@ public class ConfigureFormTest
public void getConfigFormWithTimeout() throws XMPPException, SmackException, InterruptedException, XmppStringprepException
{
ThreadedDummyConnection con = new ThreadedDummyConnection();
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = new PubSubManager(con, PubSubManagerTest.DUMMY_PUBSUB_SERVICE);
DiscoverInfo info = new DiscoverInfo();
Identity ident = new Identity("pubsub", null, "leaf");
info.addIdentity(ident);

View File

@ -25,13 +25,29 @@ import org.jivesoftware.smack.ThreadedDummyConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.junit.Test;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class PubSubManagerTest {
public static final DomainBareJid DUMMY_PUBSUB_SERVICE;
static {
DomainBareJid pubSubService;
try {
pubSubService = JidCreate.domainBareFrom("pubsub.dummy.org");
}
catch (XmppStringprepException e) {
throw new AssertionError(e);
}
DUMMY_PUBSUB_SERVICE = pubSubService;
}
@Test
public void deleteNodeTest() throws InterruptedException, SmackException, IOException, XMPPException {
ThreadedDummyConnection con = ThreadedDummyConnection.newInstance();
PubSubManager mgr = new PubSubManager(con);
PubSubManager mgr = new PubSubManager(con, DUMMY_PUBSUB_SERVICE);
mgr.deleteNode("foo@bar.org");

View File

@ -0,0 +1,66 @@
/**
*
* Copyright 2015 Florian Schmaus
*
* 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.pubsub;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jxmpp.jid.DomainBareJid;
public class PubSubIntegrationTest extends AbstractSmackIntegrationTest {
private final PubSubManager pubSubManagerOne;
public PubSubIntegrationTest(SmackIntegrationTestEnvironment environment)
throws TestNotPossibleException, NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException {
super(environment);
DomainBareJid pubSubService = PubSubManager.getPubSubService(conOne);
if (pubSubService == null) {
throw new TestNotPossibleException("No PubSub service found");
}
pubSubManagerOne = PubSubManager.getInstance(conOne, pubSubService);
if (!pubSubManagerOne.canCreateNodesAndPublishItems()) {
throw new TestNotPossibleException("PubSub service does not allow node creation");
}
}
@SmackIntegrationTest
public void simplePubSubNodeTest() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
final String nodename = "sinttest-simple-nodename-" + testRunId;
final String itemId = "sintest-simple-itemid-" + testRunId;
LeafNode leafNode = pubSubManagerOne.createNode(nodename);
try {
leafNode.publish(new Item(itemId));
List<Item> items = leafNode.getItems();
assertEquals(1, items.size());
Item item = items.get(0);
assertEquals(itemId, item.getId());
}
finally {
pubSubManagerOne.deleteNode(nodename);
}
}
}

View File

@ -0,0 +1 @@
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/package-info.java