rocks.xmpp.extensions.pubsub.PubSubNode Maven / Gradle / Ivy
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.extensions.pubsub;
import rocks.xmpp.addr.Jid;
import rocks.xmpp.core.session.Extension;
import rocks.xmpp.core.session.XmppSession;
import rocks.xmpp.core.stanza.model.IQ;
import rocks.xmpp.extensions.data.model.DataForm;
import rocks.xmpp.extensions.disco.ServiceDiscoveryManager;
import rocks.xmpp.extensions.disco.model.info.Identity;
import rocks.xmpp.extensions.pubsub.model.Affiliation;
import rocks.xmpp.extensions.pubsub.model.Item;
import rocks.xmpp.extensions.pubsub.model.NodeConfiguration;
import rocks.xmpp.extensions.pubsub.model.NodeMetaData;
import rocks.xmpp.extensions.pubsub.model.NodeType;
import rocks.xmpp.extensions.pubsub.model.PubSub;
import rocks.xmpp.extensions.pubsub.model.PublishOptions;
import rocks.xmpp.extensions.pubsub.model.SubscribeOptions;
import rocks.xmpp.extensions.pubsub.model.Subscription;
import rocks.xmpp.extensions.pubsub.model.owner.PubSubOwner;
import rocks.xmpp.util.concurrent.AsyncResult;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* This class represents a single pubsub node.
*
* It allows you to create the node on the pubsub service, subscribe or unsubscribe from the node, retrieve items from it, publish items to it, etc.
*
* @author Christian Schudt
*/
public final class PubSubNode {
private final Jid pubSubServiceAddress;
private final XmppSession xmppSession;
private final ServiceDiscoveryManager serviceDiscoveryManager;
private volatile NodeType type;
private volatile String nodeId;
PubSubNode(String nodeId, Jid pubSubServiceAddress, XmppSession xmppSession) {
// If a node is a leaf node rather than a collection node and items have been published to the node, the service MAY return one element for each published item as described in the Discover Items for a Node section of this document, however such items MUST NOT include a 'node' attribute (since they are published items, not nodes).
this(nodeId, nodeId == null ? NodeType.LEAF : NodeType.COLLECTION, pubSubServiceAddress, xmppSession);
}
PubSubNode(String nodeId, NodeType type, Jid pubSubServiceAddress, XmppSession xmppSession) {
this.nodeId = nodeId;
this.type = type;
this.pubSubServiceAddress = pubSubServiceAddress;
this.xmppSession = xmppSession;
this.serviceDiscoveryManager = xmppSession.getManager(ServiceDiscoveryManager.class);
}
/**
* Discovers the node info, which consists of a node name, type and meta data.
*
* @return The async result with the node info.
* @see 5.3 Discover Node Information
* @see 5.4 Discover Node Metadata
*/
public AsyncResult discoverNodeMetaData() {
if (nodeId == null) {
throw new IllegalStateException("nodeId must not be null.");
}
return serviceDiscoveryManager.discoverInformation(pubSubServiceAddress, nodeId).thenApply(infoNode -> {
Identity identity = null;
NodeMetaData metaDataForm = null;
if (infoNode != null) {
Set identities = infoNode.getIdentities();
Iterator iterator = identities.iterator();
if (iterator.hasNext()) {
identity = iterator.next();
}
for (DataForm dataForm : infoNode.getExtensions()) {
String formType = dataForm.getFormType();
if (NodeMetaData.FORM_TYPE.equals(formType)) {
metaDataForm = new NodeMetaData(dataForm);
break;
}
}
}
// Assign the node type.
type = identity != null ? "collection".equals(identity.getType()) ? NodeType.COLLECTION : NodeType.LEAF : NodeType.LEAF;
return metaDataForm;
});
}
/**
* Discovers the items for this node.
*
* @return The async result with the items.
* @see 5.5 Discover Items for a Node
*/
public AsyncResult> discoverItems() {
return serviceDiscoveryManager.discoverItems(pubSubServiceAddress, nodeId).thenApply(itemNode -> {
List- result = new ArrayList<>();
for (final rocks.xmpp.extensions.disco.model.items.Item item : itemNode.getItems()) {
// The 'name' attribute of each Service Discovery item MUST contain its ItemID
result.add(new Item() {
@Override
public Object getPayload() {
return null;
}
@Override
public String getId() {
return item.getName();
}
@Override
public String getNode() {
return null;
}
@Override
public String getPublisher() {
return null;
}
});
}
return result;
});
}
/**
* Gets the subscriptions for this node.
*
* @return The async result with the subscriptions for the node.
* @see 5.6 Retrieve Subscriptions
*/
public AsyncResult
> getSubscriptions() {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withSubscriptions(nodeId))).thenApply(result ->
result.getExtension(PubSub.class).getSubscriptions());
}
/**
* Gets the affiliations for this node.
*
* @return The async result with the affiliations for all nodes.
* @see 5.7 Retrieve Affiliations
*/
public AsyncResult> getAffiliations() {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withAffiliations(nodeId))).thenApply(result ->
result.getExtension(PubSub.class).getAffiliations());
}
/**
* Subscribes to this node.
*
* @return The async result with the subscription.
* @see 6.1 Subscribe to a Node
*/
public AsyncResult subscribe() {
return subscribe(null);
}
/**
* Subscribes to and configures this node.
*
* @param subscribeOptions The configuration form.
* @return The async result with the subscription.
* @see 6.3.7 Subscribe and Configure
*/
public AsyncResult subscribe(SubscribeOptions subscribeOptions) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withSubscribe(Objects.requireNonNull(nodeId, "nodeId must not be null"), xmppSession.getConnectedResource().asBareJid(), subscribeOptions != null ? subscribeOptions.getDataForm() : null))).thenApply(result ->
result.getExtension(PubSub.class).getSubscription());
}
/**
* Unsubscribes from this node.
*
* @see 6.2 Unsubscribe from a Node
*/
public void unsubscribe() {
this.unsubscribe(null);
}
/**
* Unsubscribes from this node.
*
* @param subscriptionId The subscription id.
* @return The async result.
* @see 6.2 Unsubscribe from a Node
*/
public AsyncResult unsubscribe(String subscriptionId) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withUnsubscribe(nodeId, xmppSession.getConnectedResource().asBareJid(), subscriptionId)));
}
/**
* Gets the (default) subscription options for this node.
*
* @param defaultOptions Whether to get the default options or not.
* @return The async result with the data form.
* @see 6.3.2 Request
* @see 6.4 Request Default Subscription Configuration Options
* @see #configureSubscription(rocks.xmpp.extensions.pubsub.model.SubscribeOptions)
*/
public AsyncResult getSubscriptionOptions(boolean defaultOptions) {
if (defaultOptions) {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withDefault(nodeId))).thenApply(result ->
new SubscribeOptions(result.getExtension(PubSub.class).getDefault().getDataForm()));
} else {
return getSubscriptionOptions();
}
}
/**
* Gets the subscription options for this node.
*
* @return The async result with the data form.
* @see 6.3.2 Request
* @see #configureSubscription(rocks.xmpp.extensions.pubsub.model.SubscribeOptions)
*/
public AsyncResult getSubscriptionOptions() {
return getSubscriptionOptions(null);
}
/**
* Gets the subscription options for this node.
*
* @param subId The subscription id.
* @return The async result with the data form.
* @see 6.3.2 Request
* @see #configureSubscription(rocks.xmpp.extensions.pubsub.model.SubscribeOptions)
*/
public AsyncResult getSubscriptionOptions(String subId) {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withOptions(nodeId, xmppSession.getConnectedResource().asBareJid(), subId, null))).thenApply(result ->
new SubscribeOptions(result.getExtension(PubSub.class).getOptions().getDataForm()));
}
/**
* Configures the subscription options for this node.
*
* @param subscribeOptions The subscription options form.
* @return The async result.
* @see 6.3.5 Form Submission
*/
public AsyncResult configureSubscription(SubscribeOptions subscribeOptions) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withOptions(nodeId, xmppSession.getConnectedResource().asBareJid(), null, subscribeOptions != null ? subscribeOptions.getDataForm() : null)));
}
/**
* Gets all items for this node.
*
* @return The async result with the items.
* @see 6.5.2 Requesting All Items
*/
public AsyncResult> getItems() {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withItems(nodeId))).thenApply(result ->
result.getExtension(PubSub.class).getItems());
}
/**
* Gets one or more items with a given item id for a specific node.
*
* @param ids The item ids.
* @return The async result with the items.
* @see 6.5.6 Returning Notifications Only
* @see 6.5.8 Requesting a Particular Item
*/
public AsyncResult> getItems(String... ids) {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withItems(nodeId, ids))).thenApply(result ->
result.getExtension(PubSub.class).getItems());
}
/**
* Gets the most recent items.
*
* @param maxItems The maximal number of items.
* @return The async result with the items.
* @see 6.5.7 Requesting the Most Recent Items
*/
public AsyncResult> getItems(int maxItems) {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSub.withItems(nodeId, maxItems))).thenApply(result ->
result.getExtension(PubSub.class).getItems());
}
/**
* Publishes an item to this node.
*
* @param item The item to be published. Note that this item must be known to the session, so that it can be marshalled into XML.
* @return The async result with the item id, generated by the pubsub service.
* @see 7.1 Publish an Item to a Node
* @see rocks.xmpp.core.session.XmppSessionConfiguration.Builder#extensions(Extension...)
*/
public AsyncResult publish(Object item) {
return publish(null, item);
}
/**
* Publishes an item to this node.
*
* @param item The item to be published. Note that this item must be known to the session, so that it can be marshalled into XML.
* @param publishOptions The optional publish options.
* @return The async result with the item id, generated by the pubsub service.
* @see 7.1 Publish an Item to a Node
* @see rocks.xmpp.core.session.XmppSessionConfiguration.Builder#extensions(Extension...)
*/
public AsyncResult publish(Object item, PublishOptions publishOptions) {
return publish(null, item, publishOptions);
}
/**
* Publishes an item to this node.
*
* @param id The item's id.
* @param item The item to be published. Note that this item must be known to the session, so that it can be marshalled into XML.
* @return The async result with the item id.
* @see 7.1 Publish an Item to a Node
* @see rocks.xmpp.core.session.XmppSessionConfiguration.Builder#extensions(Extension...)
*/
public AsyncResult publish(String id, Object item) {
return publish(id, item, null);
}
/**
* Publishes an item to this node.
*
* @param id The item's id.
* @param item The item to be published. Note that this item must be known to the session, so that it can be marshalled into XML.
* @param publishOptions The optional publish options.
* @return The async result with the item id.
* @see 7.1.5 Publishing Options
* @see rocks.xmpp.core.session.XmppSessionConfiguration.Builder#extensions(Extension...)
*/
public AsyncResult publish(String id, Object item, PublishOptions publishOptions) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withPublish(nodeId, id, item, publishOptions != null ? publishOptions.getDataForm() : null))).thenApply(result -> {
PubSub pubSub = result.getExtension(PubSub.class);
if (pubSub != null && pubSub.getPublish() != null && pubSub.getPublish().getItem() != null) {
return pubSub.getPublish().getItem().getId();
}
return id;
});
}
/**
* Deletes an item from this node.
*
* @param id The item id.
* @param notify If the pubsub service shall notify the subscribers about the deletion.
* @return The async result.
* @see 7.2 Delete an Item from a Node
*/
public AsyncResult deleteItem(String id, boolean notify) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withRetract(nodeId, id, notify)));
}
/**
* Creates the node on the remote pubsub service.
*
* @return The async result with the node id, if it wasn't already set.
* @see 8.1 Create a Node
*/
public AsyncResult create() {
return create(null);
}
/**
* Creates and configures this node on the remote pubsub service.
*
* @param nodeConfiguration The configuration form.
* @return The async result with the node id.
* @see 8.1.3 Create and Configure a Node
*/
public AsyncResult create(NodeConfiguration nodeConfiguration) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSub.withCreate(nodeId, nodeConfiguration != null ? nodeConfiguration.getDataForm() : null))).thenApply(result -> {
if (nodeId != null) {
return nodeId;
}
PubSub pubSub = result.getExtension(PubSub.class);
if (pubSub != null) {
String generatedNodeId = pubSub.getNode();
if (generatedNodeId != null) {
this.nodeId = generatedNodeId;
}
}
return nodeId;
});
}
/**
* Gets the node configuration form.
*
* @return The async result with the configuration form.
* @see 8.2.1 Request
*/
public AsyncResult getNodeConfiguration() {
return xmppSession.query(IQ.get(pubSubServiceAddress, PubSubOwner.withConfigure(nodeId))).thenApply(result -> {
PubSubOwner pubSubOwner = result.getExtension(PubSubOwner.class);
return new NodeConfiguration(pubSubOwner.getConfigurationForm());
});
}
/**
* Configures the node by submitting the configuration form.
*
* @param nodeConfiguration The configuration form.
* @return The async result.
* @see 8.2.4 Form Submission
*/
public AsyncResult configureNode(NodeConfiguration nodeConfiguration) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSubOwner.withConfigure(nodeId, nodeConfiguration.getDataForm())));
}
/**
* Deletes this node on the pubsub service.
*
* @return The async result.
* @see 8.4 Delete a Node
*/
public AsyncResult delete() {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSubOwner.withDelete(nodeId)));
}
/**
* Deletes this node and specifies a replacement node.
*
* @param uri The replacement node.
* @return The async result.
* @see 8.4 Delete a Node
*/
public AsyncResult delete(URI uri) {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSubOwner.withDelete(nodeId, uri)));
}
/**
* Purges this node of all published items.
*
* @return The async result.
* @see 8.5 Purge All Node Items
*/
public AsyncResult purge() {
return xmppSession.query(IQ.set(pubSubServiceAddress, PubSubOwner.withPurge(nodeId)));
}
/**
* Discovers the (sub-)nodes, which hierarchically reside under this node, e.g. the "second-level" nodes.
*
* @return The async result with the discovered pubsub nodes.
* @see 5.2 Discover Nodes
*/
public AsyncResult> discoverNodes() {
return serviceDiscoveryManager.discoverItems(pubSubServiceAddress, nodeId).thenApply(itemNode ->
itemNode.getItems().stream()
.map(item -> new PubSubNode(item.getNode(), pubSubServiceAddress, xmppSession))
.collect(Collectors.toList()));
}
/**
* Gets the node id.
*
* @return The node id.
*/
public String getId() {
return nodeId;
}
/**
* Gets the type of this node.
*
* @return The type.
*/
public NodeType getType() {
return type;
}
/**
* The node id.
*
* @return The node id.
*/
@Override
public String toString() {
return nodeId;
}
}