Commit 5a6409d8 authored by Matt Tucker's avatar Matt Tucker Committed by matt

Latest pubsub work (JM-613).

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3652 b35dd754-fafc-0310-a699-88a17e54d16e
parent 75a3c4f4
......@@ -12,9 +12,11 @@
package org.jivesoftware.wildfire.pubsub;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.forms.FormField;
import org.xmpp.forms.DataForm;
import org.jivesoftware.util.LocaleUtils;
import org.dom4j.Element;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
......@@ -49,10 +51,6 @@ public class CollectionNode extends Node {
*/
private int maxLeafNodes = -1;
// TODO Send event notification when a new child node is added (section 9.2)
// TODO Add checking that max number of leaf nodes has been reached
// TODO Add checking that verifies that user that is associating leaf node with collection node is allowed
CollectionNode(PubSubService service, CollectionNode parentNode, String nodeID, JID creator) {
super(service, parentNode, nodeID, creator);
// Configure node with default values (get them from the pubsub service)
......@@ -77,7 +75,9 @@ public class CollectionNode extends Node {
try {
associationTrusted.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
else if ("pubsub#leaf_nodes_max".equals(field.getVariable())) {
......@@ -123,15 +123,96 @@ public class CollectionNode extends Node {
formField.addValue(maxLeafNodes);
}
/**
* Adds a child node to the list of child nodes. The new child node may just have been
* created or just restored from the database. This method will not trigger notifications
* to node subscribers since the node could be a node that has just been loaded from the
* database.
*
* @param child the node to add to the list of child nodes.
*/
void addChildNode(Node child) {
nodes.put(child.getNodeID(), child);
}
/**
* Removes a child node from the list of child nodes. This method will not trigger
* notifications to node subscribers.
*
* @param child the node to remove from the list of child nodes.
*/
void removeChildNode(Node child) {
// TODO Send notification to subscribers?
nodes.remove(child.getNodeID());
}
/**
* Notification that a new node was created and added to this node. Trigger notifications
* to node subscribers whose subscription type is {@link NodeSubscription.Type#nodes} and
* have the proper depth.
*
* @param child the newly created node that was added to this node.
*/
void childNodeAdded(Node child) {
// Build packet to broadcast to subscribers
Message message = new Message();
Element event = message.addChildElement("event", "http://jabber.org/protocol/pubsub#event");
Element item = event.addElement("items").addElement("item");
item.addAttribute("id", child.getNodeID());
if (deliverPayloads) {
item.add(child.getMetadataForm().getElement());
}
// Broadcast event notification to subscribers
broadcastCollectionNodeEvent(child, message);
}
/**
* Notification that a child node was deleted from this node. Trigger notifications
* to node subscribers whose subscription type is {@link NodeSubscription.Type#nodes} and
* have the proper depth.
*
* @param child the deleted node that was removed from this node.
*/
void childNodeDeleted(Node child) {
// Build packet to broadcast to subscribers
Message message = new Message();
Element event = message.addChildElement("event", "http://jabber.org/protocol/pubsub#event");
event.addElement("delete").addAttribute("node", child.getNodeID());
// Broadcast event notification to subscribers
broadcastCollectionNodeEvent(child, message);
}
private void broadcastCollectionNodeEvent(Node child, Message notification) {
// Get affected subscriptions (of this node and all parent nodes)
Collection<NodeSubscription> subscriptions = new ArrayList<NodeSubscription>();
subscriptions.addAll(getSubscriptions(child));
for (CollectionNode parentNode : getParents()) {
subscriptions.addAll(parentNode.getSubscriptions(child));
}
// TODO Possibly use a thread pool for sending packets (based on the jids size)
for (NodeSubscription subscription : subscriptions) {
service.sendNotification(subscription.getNode(), notification, subscription.getJID());
}
}
/**
* Returns a collection with the subscriptions to this node that should be notified
* that a new child was added or deleted.
*
* @param child the added or deleted child.
* @return a collection with the subscriptions to this node that should be notified
* that a new child was added or deleted.
*/
private Collection<NodeSubscription> getSubscriptions(Node child) {
Collection<NodeSubscription> subscriptions = new ArrayList<NodeSubscription>();
for (NodeSubscription subscription : getSubscriptions()) {
if (subscription.canSendChildNodeEvent(child)) {
subscriptions.add(subscription);
}
}
return subscriptions;
}
public boolean isCollectionNode() {
return true;
}
......@@ -232,6 +313,54 @@ public class CollectionNode extends Node {
this.maxLeafNodes = maxLeafNodes;
}
/**
* Returns true if the specified user is allowed to associate a leaf node with this
* node. The decision is taken based on the association policy that the node is
* using.
*
* @param user the user trying to associate a leaf node with this node.
* @return true if the specified user is allowed to associate a leaf node with this
* node.
*/
public boolean isAssociationAllowed(JID user) {
if (associationPolicy == LeafNodeAssociationPolicy.all) {
// Anyone is allowed to associate leaf nodes with this node
return true;
}
else if (associationPolicy == LeafNodeAssociationPolicy.owners) {
// Only owners or sysadmins are allowed to associate leaf nodes with this node
return isAdmin(user);
}
else {
// Owners, sysadmins and a whitelist of usres are allowed to
// associate leaf nodes with this node
return isAdmin(user) || associationTrusted.contains(user);
}
}
/**
* Returns true if the max number of leaf nodes associated with this node has
* reached to the maximum allowed.
*
* @return true if the max number of leaf nodes associated with this node has
* reached to the maximum allowed.
*/
public boolean isMaxLeafNodeReached() {
if (maxLeafNodes < 0) {
// There is no maximum limit
return false;
}
// Count number of child leaf nodes
int counter = 0;
for (Node node : getNodes()) {
if (!node.isCollectionNode()) {
counter = counter + 1;
}
}
// Compare count with maximum allowed
return counter >= maxLeafNodes;
}
/**
* Policy that defines who may associate leaf nodes with a collection.
*/
......@@ -248,6 +377,6 @@ public class CollectionNode extends Node {
/**
* Only those on a whitelist may associate leaf nodes with the collection.
*/
whitelist;
whitelist
}
}
......@@ -18,6 +18,7 @@ import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.IQ;
import java.util.*;
......@@ -55,10 +56,10 @@ public class LeafNode extends Node {
* not configured to persist items then the last published item will be kept. The list is
* sorted cronologically.
*/
protected List<PublishedItem> publishedItems = new ArrayList<PublishedItem>();
protected final List<PublishedItem> publishedItems = new ArrayList<PublishedItem>();
protected Map<String, PublishedItem> itemsByID = new HashMap<String, PublishedItem>();
// TODO Add checking of max payload size
// TODO Add checking of max payload size. Return <not-acceptable> plus a application specific error condition of <payload-too-big/>.
LeafNode(PubSubService service, CollectionNode parentNode, String nodeID, JID creator) {
super(service, parentNode, nodeID, creator);
......@@ -213,7 +214,7 @@ public class LeafNode extends Node {
if (isItemRequired()) {
String itemID;
Element payload;
PublishedItem newItem = null;
PublishedItem newItem;
for (Element item : itemElements) {
itemID = item.attributeValue("id");
List entries = item.elements();
......@@ -316,6 +317,35 @@ public class LeafNode extends Node {
}
}
/**
* Sends an IQ result with the list of items published to the node. Item ID and payload
* may be included in the result based on the node configuration.
*
* @param originalRequest the IQ packet sent by a subscriber (or anyone) to get the node items.
* @param publishedItems the list of published items to send to the subscriber.
* @param forceToIncludePayload true if the item payload should be include if one exists. When
* false the decision is up to the node.
*/
void sendPublishedItems(IQ originalRequest, List<PublishedItem> publishedItems,
boolean forceToIncludePayload) {
IQ result = IQ.createResultIQ(originalRequest);
Element childElement = originalRequest.getChildElement().createCopy();
result.setChildElement(childElement);
Element items = childElement.element("items");
for (PublishedItem publishedItem : publishedItems) {
Element item = items.addElement("item");
if (isItemRequired()) {
item.addAttribute("id", publishedItem.getID());
}
if ((forceToIncludePayload || isPayloadDelivered()) &&
publishedItem.getPayload() != null) {
item.add(publishedItem.getPayload().createCopy());
}
}
// Send the result
service.send(result);
}
public PublishedItem getPublishedItem(String itemID) {
if (!isItemRequired()) {
return null;
......
......@@ -216,15 +216,24 @@ public abstract class Node {
}
/**
* Removes the owner affiliation of the specified entity JID. If the user was an owner
* of this node then the user will not have any affiliation with the node.
* Removes the owner affiliation of the specified entity JID. If the user that is
* no longer an owner was subscribed to the node then his affiliation will be of
* type {@link NodeAffiliate.Affiliation#none}.
*
* @param jid the JID of the user being removed as a node owner.
*/
public void removeOwner(JID jid) {
// Get the current affiliation of the specified JID
NodeAffiliate affiliate = getAffiliate(jid);
if (affiliate.getSubscriptions().isEmpty()) {
removeAffiliation(jid, NodeAffiliate.Affiliation.owner);
removeSubscriptions(jid);
}
else {
// The user has subscriptions so change affiliation to NONE
addNoneAffiliation(jid);
}
}
/**
* Adds a new affiliation or updates an existing affiliation of the specified entity JID
......@@ -250,15 +259,24 @@ public abstract class Node {
}
/**
* Removes the publisher affiliation of the specified entity JID. If the user was a publisher
* of this node then the user will not have any affiliation with the node.
* Removes the publisher affiliation of the specified entity JID. If the user that is
* no longer a publisher was subscribed to the node then his affiliation will be of
* type {@link NodeAffiliate.Affiliation#none}.
*
* @param jid the JID of the user being removed as a node publisher.
*/
public void removePublisher(JID jid) {
// Get the current affiliation of the specified JID
NodeAffiliate affiliate = getAffiliate(jid);
if (affiliate.getSubscriptions().isEmpty()) {
removeAffiliation(jid, NodeAffiliate.Affiliation.publisher);
removeSubscriptions(jid);
}
else {
// The user has subscriptions so change affiliation to NONE
addNoneAffiliation(jid);
}
}
/**
* Adds a new affiliation or updates an existing affiliation of the specified entity JID
......@@ -321,9 +339,8 @@ public abstract class Node {
private void removeAffiliation(JID jid, NodeAffiliate.Affiliation affiliation) {
// Get the current affiliation of the specified JID
NodeAffiliate affiliate = getAffiliate(jid);
// Check if the user already has the same affiliation
// Check if the current affiliatin of the user is the one to remove
if (affiliate != null && affiliation == affiliate.getAffiliation()) {
// TODO If user has subscriptions then change affiliation to NONE
removeAffiliation(affiliate);
}
}
......@@ -440,6 +457,8 @@ public abstract class Node {
* @throws NotAcceptableException if completed data form tries to leave the node without owners.
*/
public void configure(DataForm completedForm) throws NotAcceptableException {
boolean wasPresenceBased = isPresenceBasedDelivery();
if (DataForm.Type.cancel.equals(completedForm.getType())) {
// Existing node configuration is applied (i.e. nothing is changed)
}
......@@ -456,7 +475,9 @@ public abstract class Node {
try {
owners.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
......@@ -543,7 +564,9 @@ public abstract class Node {
try {
contacts.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
else if ("pubsub#description".equals(field.getVariable())) {
......@@ -571,7 +594,9 @@ public abstract class Node {
try {
replyRooms.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
else if ("pubsub#replyto".equals(field.getVariable())) {
......@@ -581,7 +606,9 @@ public abstract class Node {
try {
replyTo.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
else {
......@@ -615,7 +642,9 @@ public abstract class Node {
try {
publishers.add(new JID(value));
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
// Calculate publishers to remove and remove them from the DB
Collection<JID> oldPublishers = getPublishers();
......@@ -643,6 +672,16 @@ public abstract class Node {
}
// Store the new or updated node in the backend store
saveToDB();
// Check if we need to subscribe or unsubscribe from affiliate presences
if (wasPresenceBased != isPresenceBasedDelivery()) {
if (isPresenceBasedDelivery()) {
addPresenceSubscriptions();
}
else {
cancelPresenceSubscriptions();
}
}
}
/**
......@@ -1146,7 +1185,6 @@ public abstract class Node {
* @return true if notifications are going to be delivered to available users only.
*/
public boolean isPresenceBasedDelivery() {
// TODO Use this variable somewhere :)
return presenceBasedDelivery;
}
......@@ -1556,7 +1594,7 @@ public abstract class Node {
// Make the room persistent
if (!savedToDB) {
PubSubPersistenceManager.createNode(service, this);
// Set that the now is now in the DB
// Set that the node is now in the DB
setSavedToDB(true);
// Save the existing node affiliates to the DB
for (NodeAffiliate affialiate : affiliates) {
......@@ -1568,6 +1606,10 @@ public abstract class Node {
}
// Add the new node to the list of available nodes
service.addNode(this);
// Notify the parent (if any) that a new node has been added
if (parent != null) {
parent.childNodeAdded(this);
}
}
else {
PubSubPersistenceManager.updateNode(service, this);
......@@ -1628,7 +1670,6 @@ public abstract class Node {
* @return true if the node was successfully deleted.
*/
public boolean delete() {
// TODO Should we lock the object to prevent simultaneous edition, publishing, etc.????
// Delete node from the database
if (PubSubPersistenceManager.removeNode(service, this)) {
// Remove this node from the parent node (if any)
......@@ -1650,6 +1691,12 @@ public abstract class Node {
// Send notification that the node was deleted
broadcastNodeEvent(message, true);
}
// Notify the parent (if any) that the node has been removed from the parent node
if (parent != null) {
parent.childNodeDeleted(this);
}
// Remove presence subscription when node was deleted.
cancelPresenceSubscriptions();
// Remove the node from memory
service.removeNode(getNodeID());
// Clear collections in memory (clear them after broadcast was sent)
......@@ -1661,6 +1708,33 @@ public abstract class Node {
return false;
}
/**
* Unsubscribe from affiliates presences if node is only sending notifications to
* only users or only unsubscribe from those subscribers that configured their
* subscription to send notifications based on their presence show value.
*/
private void addPresenceSubscriptions() {
for (NodeAffiliate affiliate : affiliates) {
if (affiliate.getAffiliation() != NodeAffiliate.Affiliation.outcast &&
(isPresenceBasedDelivery() || (!affiliate.getSubscriptions().isEmpty()))) {
service.presenceSubscriptionRequired(this, affiliate.getJID());
}
}
}
/**
* Unsubscribe from affiliates presences if node is only sending notifications to
* only users or only unsubscribe from those subscribers that configured their
* subscription to send notifications based on their presence show value.
*/
private void cancelPresenceSubscriptions() {
for (NodeSubscription subscription : getSubscriptions()) {
if (isPresenceBasedDelivery() || !subscription.getPresenceStates().isEmpty()) {
service.presenceSubscriptionNotRequired(this, subscription.getOwner());
}
}
}
/**
* Sends the list of affiliated entities with the node to the owner that sent the IQ
* request.
......@@ -1756,14 +1830,14 @@ public abstract class Node {
}
// Figure out subscription status
NodeSubscription.State subState = NodeSubscription.State.subscribed;
if (authorizationRequired) {
// Node owner needs to authorize subscription request so status is pending
subState = NodeSubscription.State.pending;
}
else if (isSubscriptionConfigurationRequired()) {
if (isSubscriptionConfigurationRequired()) {
// User has to configure the subscription to make it active
subState = NodeSubscription.State.unconfigured;
}
else if (authorizationRequired && !isAdmin(owner)) {
// Node owner needs to authorize subscription request so status is pending
subState = NodeSubscription.State.pending;
}
// Generate a subscription ID (override even if one was sent by the client)
String id = StringUtils.randomString(40);
// Create new subscription
......@@ -1799,6 +1873,16 @@ public abstract class Node {
subscription.sendLastPublishedItem(lastItem);
}
}
// Check if we need to subscribe to the presence of the owner
if (isPresenceBasedDelivery() && getSubscriptions(subscription.getOwner()).size() == 1) {
if (subscription.getPresenceStates().isEmpty()) {
// Subscribe to the owner's presence since the node is only sending events to
// online subscribers and this is the first subscription of the user and the
// subscription is not filtering notifications based on presence show values.
service.presenceSubscriptionRequired(this, owner);
}
}
}
/**
......@@ -1823,6 +1907,10 @@ public abstract class Node {
// Remove the subscription from the database
PubSubPersistenceManager.removeSubscription(service, this, subscription);
}
// Check if we need to unsubscribe from the presence of the owner
if (isPresenceBasedDelivery() && getSubscriptions(subscription.getOwner()).isEmpty()) {
service.presenceSubscriptionNotRequired(this, subscription.getOwner());
}
}
/**
......@@ -1937,6 +2025,6 @@ public abstract class Node {
/**
* Dynamically specify a replyto of the item publisher.
*/
publisher;
publisher
}
}
......@@ -72,15 +72,15 @@ public class NodeAffiliate {
* with the items to include in each notification.
* @param event the event Element included in the notification message. Passed as an
* optimization to avoid future look ups.
* @param node the leaf node where the items where published.
* @param leafNode the leaf node where the items where published.
* @param publishedItems the list of items that were published. Could be an empty list.
*/
void sendPublishedNotifications(Message notification, Element event, LeafNode node,
void sendPublishedNotifications(Message notification, Element event, LeafNode leafNode,
List<PublishedItem> publishedItems) {
if (!publishedItems.isEmpty()) {
Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
getItemsBySubscriptions(node, publishedItems);
getItemsBySubscriptions(leafNode, publishedItems);
// Send one notification for published items that affect the same subscriptions
for (List<NodeSubscription> nodeSubscriptions : itemsBySubs.keySet()) {
......@@ -90,37 +90,37 @@ public class NodeAffiliate {
for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
// Add item information to the event notification
Element item = items.addElement("item");
if (node.isItemRequired()) {
if (leafNode.isItemRequired()) {
item.addAttribute("id", publishedItem.getID());
}
if (node.isPayloadDelivered()) {
if (leafNode.isPayloadDelivered()) {
item.add(publishedItem.getPayload().createCopy());
}
// Add leaf node information if affiliated node and node
// Add leaf leafNode information if affiliated leafNode and node
// where the item was published are different
if (node != getNode()) {
item.addAttribute("node", node.getNodeID());
if (leafNode != getNode()) {
item.addAttribute("node", leafNode.getNodeID());
}
}
// Send the event notification
sendEventNotification(notification, node, nodeSubscriptions);
sendEventNotification(notification, nodeSubscriptions);
// Remove the added items information
event.remove(items);
}
}
else {
// Filter affiliate subscriptions and only use approved and configured ones
List<NodeSubscription> affectedSubscriptions = new ArrayList<NodeSubscription>();;
List<NodeSubscription> affectedSubscriptions = new ArrayList<NodeSubscription>();
for (NodeSubscription subscription : getSubscriptions()) {
if (subscription.canSendEventNotification(node, null)) {
if (subscription.canSendPublicationEvent(leafNode, null)) {
affectedSubscriptions.add(subscription);
}
}
// Add item information to the event notification
Element items = event.addElement("items");
items.addAttribute("node", node.getNodeID());
items.addAttribute("node", leafNode.getNodeID());
// Send the event notification
sendEventNotification(notification, node, affectedSubscriptions);
sendEventNotification(notification, affectedSubscriptions);
// Remove the added items information
event.remove(items);
}
......@@ -137,30 +137,30 @@ public class NodeAffiliate {
* with the items to include in each notification.
* @param event the event Element included in the notification message. Passed as an
* optimization to avoid future look ups.
* @param node the leaf node where the items where deleted from.
* @param leafNode the leaf node where the items where deleted from.
* @param publishedItems the list of items that were deleted.
*/
void sendDeletionNotifications(Message notification, Element event, LeafNode node,
void sendDeletionNotifications(Message notification, Element event, LeafNode leafNode,
List<PublishedItem> publishedItems) {
if (!publishedItems.isEmpty()) {
Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
getItemsBySubscriptions(node, publishedItems);
getItemsBySubscriptions(leafNode, publishedItems);
// Send one notification for published items that affect the same subscriptions
for (List<NodeSubscription> nodeSubscriptions : itemsBySubs.keySet()) {
// Add items information
Element items = event.addElement("items");
items.addAttribute("node", node.getNodeID());
items.addAttribute("node", leafNode.getNodeID());
for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
// Add retract information to the event notification
Element item = items.addElement("retract");
if (node.isItemRequired()) {
if (leafNode.isItemRequired()) {
item.addAttribute("id", publishedItem.getID());
}
}
// Send the event notification
sendEventNotification(notification, node, nodeSubscriptions);
sendEventNotification(notification, nodeSubscriptions);
// Remove the added items information
event.remove(items);
}
......@@ -180,11 +180,10 @@ public class NodeAffiliate {
* specified), the subscription status and originating node.
*
* @param notification the message to send containing the event notification.
* @param node the node that received a new publication.
* @param notifySubscriptions list of subscriptions that were affected and are going to be
* included in the notification message. The list should not be empty.
*/
private void sendEventNotification(Message notification, LeafNode node,
private void sendEventNotification(Message notification,
List<NodeSubscription> notifySubscriptions) {
if (node.isMultipleSubscriptionsEnabled()) {
// Group subscriptions with the same subscriber JID
......@@ -215,8 +214,8 @@ public class NodeAffiliate {
}
}
private Map<List<NodeSubscription>, List<PublishedItem>> getItemsBySubscriptions(LeafNode node,
List<PublishedItem> publishedItems) {
private Map<List<NodeSubscription>, List<PublishedItem>> getItemsBySubscriptions(
LeafNode leafNode, List<PublishedItem> publishedItems) {
// Identify which subscriptions can receive each item
Map<PublishedItem, List<NodeSubscription>> subsByItem =
new HashMap<PublishedItem, List<NodeSubscription>>();
......@@ -225,7 +224,7 @@ public class NodeAffiliate {
Collection<NodeSubscription> subscriptions = getSubscriptions();
for (PublishedItem publishedItem : publishedItems) {
for (NodeSubscription subscription : subscriptions) {
if (subscription.canSendEventNotification(node, publishedItem)) {
if (subscription.canSendPublicationEvent(leafNode, publishedItem)) {
List<NodeSubscription> nodeSubscriptions = subsByItem.get(publishedItem);
if (nodeSubscriptions == null) {
nodeSubscriptions = new ArrayList<NodeSubscription>();
......@@ -277,6 +276,6 @@ public class NodeAffiliate {
/**
* Outcast users are not allowed to subscribe to the node.
*/
outcast;
outcast
}
}
......@@ -49,8 +49,8 @@ import java.util.*;
*/
public class NodeSubscription {
private static SimpleDateFormat dateFormat;
private static FastDateFormat fastDateFormat;
private static final SimpleDateFormat dateFormat;
private static final FastDateFormat fastDateFormat;
/**
* Reference to the publish and subscribe service.
*/
......@@ -412,6 +412,9 @@ public class NodeSubscription {
void configure(DataForm options) {
List<String> values;
String booleanValue;
boolean wasUsingPresence = !presenceStates.isEmpty();
// Remove this field from the form
options.removeField("FORM_TYPE");
// Process and remove specific collection node fields
......@@ -471,7 +474,9 @@ public class NodeSubscription {
try {
presenceStates.add(value);
}
catch (Exception e) {}
catch (Exception e) {
// Do nothing
}
}
}
else if ("x-pubsub#keywords".equals(field.getVariable())) {
......@@ -483,7 +488,7 @@ public class NodeSubscription {
}
if (fieldExists) {
// Subscription has been configured so set the next state
if (node.getAccessModel().isAuthorizationRequired()) {
if (node.getAccessModel().isAuthorizationRequired() && !node.isAdmin(owner)) {
state = State.pending;
}
else {
......@@ -495,6 +500,15 @@ public class NodeSubscription {
// Update the subscription in the backend store
PubSubPersistenceManager.saveSubscription(service, node, this, false);
}
// Check if the service needs to subscribe or unsubscribe from the owner presence
if (!node.isPresenceBasedDelivery() && wasUsingPresence != !presenceStates.isEmpty()) {
if (presenceStates.isEmpty()) {
service.presenceSubscriptionNotRequired(node, owner);
}
else {
service.presenceSubscriptionRequired(node, owner);
}
}
}
/**
......@@ -601,22 +615,10 @@ public class NodeSubscription {
* @return true if an event notification can be sent to the subscriber for the specified
* published item.
*/
boolean canSendEventNotification(LeafNode leafNode, PublishedItem publishedItem) {
// Check if the subscription is active
if (!isActive()) {
return false;
}
// Check if delivery of notifications is disabled
if (!shouldDeliverNotifications()) {
boolean canSendPublicationEvent(LeafNode leafNode, PublishedItem publishedItem) {
if (!canSendEvents()) {
return false;
}
// Check if delivery is subject to presence-based policy
if (!getPresenceStates().isEmpty()) {
String show = service.getShowPresence(jid);
if (show == null || !getPresenceStates().contains(show)) {
return false;
}
}
// Check that any defined keyword was matched (applies only if an item was published)
if (publishedItem != null && !isKeywordMatched(publishedItem)) {
return false;
......@@ -639,6 +641,39 @@ public class NodeSubscription {
return true;
}
/**
* Returns true if an event notification can be sent to the subscriber of the collection
* node for a newly created node that was associated to the collection node or a child
* node that was deleted. The subscription has to be of type {@link Type#nodes}.
*
* @param originatingNode the node that was added or deleted from the collection node.
* @return true if an event notification can be sent to the subscriber of the collection
* node.
*/
boolean canSendChildNodeEvent(Node originatingNode) {
// Check that this is a subscriber to a collection node
if (!node.isCollectionNode()) {
return false;
}
if (!canSendEvents()) {
return false;
}
// Check that subscriber is using type "nodes"
if (Type.nodes != type) {
return false;
}
// Check if added/deleted node is a first-level child of the subscribed node
if (getDepth() == 1 && !node.isChildNode(originatingNode)) {
return false;
}
// Check if added/deleted node is a descendant child of the subscribed node
if (getDepth() == 0 && !node.isDescendantNode(originatingNode)) {
return false;
}
return true;
}
/**
* Returns true if node events such as configuration changed or node purged can be
* sent to the subscriber.
......@@ -647,6 +682,17 @@ public class NodeSubscription {
* sent to the subscriber.
*/
boolean canSendNodeEvents() {
return canSendEvents();
}
/**
* Returns true if events in general can be sent. This method checks basic
* conditions common to all type of event notifications (e.g. item was published,
* node configuration has changed, new child node was added to collection node, etc.).
*
* @return true if events in general can be sent.
*/
private boolean canSendEvents() {
// Check if the subscription is active
if (!isActive()) {
return false;
......@@ -657,8 +703,15 @@ public class NodeSubscription {
}
// Check if delivery is subject to presence-based policy
if (!getPresenceStates().isEmpty()) {
String show = service.getShowPresence(jid);
if (show == null || !getPresenceStates().contains(show)) {
Collection<String> shows = service.getShowPresences(jid);
if (shows.isEmpty() || Collections.disjoint(getPresenceStates(), shows)) {
return false;
}
}
// Check if node is only sending events when user is online
if (node.isPresenceBasedDelivery()) {
// Check that user is online
if (service.getShowPresences(jid).isEmpty()) {
return false;
}
}
......@@ -731,42 +784,6 @@ public class NodeSubscription {
service.send(result);
}
/**
* Sends an IQ result with the list of items published to the node to the subscriber. The
* items to include in the result is subject to the subscription configuration. If the
* subscription is still pending to be approved or unconfigured then no items will be included.
*
* @param originalRequest the IQ packet sent by the subscriber to get the node items.
* @param publishedItems the list of published items to send to the subscriber.
* @param forceToIncludePayload true if the item payload should be include if one exists. When
* false the decision is up to the node.
*/
void sendPublishedItems(IQ originalRequest, List<PublishedItem> publishedItems,
boolean forceToIncludePayload) {
IQ result = IQ.createResultIQ(originalRequest);
Element childElement = originalRequest.getChildElement().createCopy();
result.setChildElement(childElement);
Element items = childElement.element("items");
if (isActive()) {
for (PublishedItem publishedItem : publishedItems) {
// Check if this published item can be included in the result
if (!isKeywordMatched(publishedItem)) {
continue;
}
Element item = items.addElement("item");
if (((LeafNode) node).isItemRequired()) {
item.addAttribute("id", publishedItem.getID());
}
if ((forceToIncludePayload || node.isPayloadDelivered()) &&
publishedItem.getPayload() != null) {
item.add(publishedItem.getPayload().createCopy());
}
}
}
// Send the result
service.send(result);
}
/**
* Sends an event notification for the last published item to the subscriber. If
* the subscription has not yet been authorized or is pending to be configured then
......@@ -779,7 +796,7 @@ public class NodeSubscription {
*/
void sendLastPublishedItem(PublishedItem publishedItem) {
// Check if the published item can be sent to the subscriber
if (!canSendEventNotification(publishedItem.getNode(), publishedItem)) {
if (!canSendPublicationEvent(publishedItem.getNode(), publishedItem)) {
return;
}
// Send event notification to the subscriber
......@@ -910,7 +927,7 @@ public class NodeSubscription {
* An entity is subscribed to a node. The node will send all event notifications
* (and, if configured, payloads) to the entity while it is in this state.
*/
subscribed;
subscribed
}
public static enum Type {
......@@ -922,6 +939,6 @@ public class NodeSubscription {
/**
* Receive notification of new nodes only.
*/
nodes;
nodes
}
}
......@@ -245,9 +245,18 @@ public class PubSubEngine {
* @param message the Message packet sent to the pubsub service.
*/
public void process(Message message) {
// TODO Process Messages of type error to identify possible subscribers that no longer exist
if (message.getType() == Message.Type.error) {
// See "Handling Notification-Related Errors" section
// Process Messages of type error to identify possible subscribers that no longer exist
if (message.getError().getType() == PacketError.Type.cancel) {
// TODO Assuming that owner is the bare JID (as defined in the JEP). This can be replaced with an explicit owner specified in the packet
JID owner = new JID(message.getFrom().toBareJID());
// Terminate the subscription of the entity to all nodes hosted at the service
cancelAllSubscriptions(owner);
}
else if (message.getError().getType() == PacketError.Type.auth) {
// TODO Queue the message to be sent again later (will retry a few times and
// will be discarded when the retry limit is reached)
}
}
else if (message.getType() == Message.Type.normal) {
// Check that this is an answer to an authorization request
......@@ -256,30 +265,8 @@ public class PubSubEngine {
String formType = authForm.getField("FORM_TYPE").getValues().get(0);
// Check that completed data form belongs to an authorization request
if ("http://jabber.org/protocol/pubsub#subscribe_authorization".equals(formType)) {
String nodeID = authForm.getField("pubsub#node").getValues().get(0);
String subID = authForm.getField("pubsub#subid").getValues().get(0);
String allow = authForm.getField("pubsub#allow").getValues().get(0);
boolean approved;
if ("1".equals(allow) || "true".equals(allow)) {
approved = true;
}
else if ("0".equals(allow) || "false".equals(allow)) {
approved = false;
}
else {
// Unknown allow value. Ignore completed form
Log.warn("Invalid allow value in completed authorization form: " +
message.toXML());
return;
}
// Approve or cancel the pending subscription to the node
Node node = service.getNode(nodeID);
if (node != null) {
NodeSubscription subscription = node.getSubscription(subID);
if (subscription != null) {
node.approveSubscription(subscription, approved);
}
}
// Process the answer to the authorization request
processAuthorizationAnswer(authForm, message);
}
}
}
......@@ -287,7 +274,7 @@ public class PubSubEngine {
private void publishItemsToNode(IQ iq, Element publishElement) {
String nodeID = publishElement.attributeValue("node");
Node node = null;
Node node;
if (nodeID == null) {
// No node was specified. Return bad_request error
Element pubsubError = DocumentHelper.createElement(QName.get(
......@@ -342,8 +329,8 @@ public class PubSubEngine {
return;
}
List<Element> items = new ArrayList<Element>();
List entries = null;
Element payload = null;
List entries;
Element payload;
while (itemElements.hasNext()) {
Element item = (Element) itemElements.next();
entries = item.elements();
......@@ -374,7 +361,7 @@ public class PubSubEngine {
private void deleteItems(IQ iq, Element retractElement) {
String nodeID = retractElement.attributeValue("node");
Node node = null;
Node node;
if (nodeID == null) {
// No node was specified. Return bad_request error
Element pubsubError = DocumentHelper.createElement(QName.get(
......@@ -448,7 +435,7 @@ public class PubSubEngine {
private void subscribeNode(IQ iq, Element childElement, Element subscribeElement) {
String nodeID = subscribeElement.attributeValue("node");
Node node = null;
Node node;
if (nodeID == null) {
// Entity subscribes to root collection node
node = service.getRootCollectionNode();
......@@ -568,7 +555,7 @@ public class PubSubEngine {
private void unsubscribeNode(IQ iq, Element unsubscribeElement) {
String nodeID = unsubscribeElement.attributeValue("node");
String subID = unsubscribeElement.attributeValue("subid");
Node node = null;
Node node;
if (nodeID == null) {
// Entity unsubscribes from root collection node
node = service.getRootCollectionNode();
......@@ -582,7 +569,7 @@ public class PubSubEngine {
return;
}
}
NodeSubscription subscription = null;
NodeSubscription subscription;
if (node.isMultipleSubscriptionsEnabled()) {
if (subID == null) {
// No subid was specified and the node supports multiple subscriptions
......@@ -647,7 +634,7 @@ public class PubSubEngine {
private void getSubscriptionConfiguration(IQ iq, Element childElement, Element optionsElement) {
String nodeID = optionsElement.attributeValue("node");
String subID = optionsElement.attributeValue("subid");
Node node = null;
Node node;
if (nodeID == null) {
// Entity requests subscription options of root collection node
node = service.getRootCollectionNode();
......@@ -661,7 +648,7 @@ public class PubSubEngine {
return;
}
}
NodeSubscription subscription = null;
NodeSubscription subscription;
if (node.isMultipleSubscriptionsEnabled()) {
if (subID == null) {
// No subid was specified and the node supports multiple subscriptions
......@@ -719,7 +706,7 @@ public class PubSubEngine {
private void configureSubscription(IQ iq, Element optionsElement) {
String nodeID = optionsElement.attributeValue("node");
String subID = optionsElement.attributeValue("subid");
Node node = null;
Node node;
if (nodeID == null) {
// Entity submits new subscription options of root collection node
node = service.getRootCollectionNode();
......@@ -733,7 +720,7 @@ public class PubSubEngine {
return;
}
}
NodeSubscription subscription = null;
NodeSubscription subscription;
if (node.isMultipleSubscriptionsEnabled()) {
if (subID == null) {
// No subid was specified and the node supports multiple subscriptions
......@@ -855,8 +842,8 @@ public class PubSubEngine {
private void getPublishedItems(IQ iq, Element itemsElement) {
String nodeID = itemsElement.attributeValue("node");
String subID = itemsElement.attributeValue("subid");
Node node = null;
//String subID = itemsElement.attributeValue("subid");
Node node;
if (nodeID == null) {
// Entity subscribes to root collection node
node = service.getRootCollectionNode();
......@@ -889,8 +876,15 @@ public class PubSubEngine {
accessModel.getSubsriptionErrorDetail());
return;
}
// Get the user's subscription
NodeSubscription subscription = null;
// Check that the requester is not an outcast
NodeAffiliate affiliate = node.getAffiliate(owner);
if (affiliate != null && affiliate.getAffiliation() == NodeAffiliate.Affiliation.outcast) {
sendErrorPacket(iq, PacketError.Condition.forbidden, null);
return;
}
/*// Get the user's subscription
NodeSubscription subscription;
if (node.isMultipleSubscriptionsEnabled()) {
if (subID == null) {
// No subid was specified and the node supports multiple subscriptions
......@@ -909,18 +903,9 @@ public class PubSubEngine {
return;
}
}
}
else {
subscription = node.getSubscription(subscriberJID);
if (subscription == null) {
// TODO Current version does not allow anyone to get published items. A subscription should exist.
Element pubsubError = DocumentHelper.createElement(
QName.get("not-subscribed", "http://jabber.org/protocol/pubsub#errors"));
sendErrorPacket(iq, PacketError.Condition.not_authorized, pubsubError);
return;
}
}
}*/
LeafNode leafNode = (LeafNode) node;
// Get list of items to send to the user
boolean forceToIncludePayload = false;
List<PublishedItem> items = null;
......@@ -939,13 +924,13 @@ public class PubSubEngine {
}
if (max_items != null) {
// Get the N most recent published items
items = node.getPublishedItems(recentItems);
items = leafNode.getPublishedItems(recentItems);
}
else {
List requestedItems = itemsElement.elements("item");
if (requestedItems.isEmpty()) {
// Get all the active items that were published to the node
items = node.getPublishedItems();
items = leafNode.getPublishedItems();
}
else {
// Indicate that payload should be included (if exists) no matter
......@@ -955,7 +940,7 @@ public class PubSubEngine {
for (Iterator it = requestedItems.iterator(); it.hasNext();) {
Element element = (Element) it.next();
String itemID = element.attributeValue("id");
PublishedItem item = node.getPublishedItem(itemID);
PublishedItem item = leafNode.getPublishedItem(itemID);
if (item != null) {
items.add(item);
}
......@@ -963,7 +948,7 @@ public class PubSubEngine {
}
}
// Send items to the user
subscription.sendPublishedItems(iq, items, forceToIncludePayload);
leafNode.sendPublishedItems(iq, items, forceToIncludePayload);
}
private void createNode(IQ iq, Element childElement, Element createElement) {
......@@ -1057,6 +1042,23 @@ public class PubSubEngine {
return;
}
if (parentNode != null && !collectionType) {
// Check if requester is allowed to add a new leaf child node to the parent node
if (!parentNode.isAssociationAllowed(from)) {
// User is not allowed to add child leaf node to parent node. Return an error.
sendErrorPacket(iq, PacketError.Condition.forbidden, null);
return;
}
// Check if number of child leaf nodes has not been exceeded
if (parentNode.isMaxLeafNodeReached()) {
// Max number of child leaf nodes has been reached. Return an error.
Element pubsubError = DocumentHelper.createElement(QName.get("max-nodes-exceeded",
"http://jabber.org/protocol/pubsub#errors"));
sendErrorPacket(iq, PacketError.Condition.conflict, pubsubError);
return;
}
}
// Create and configure the node
boolean conflict = false;
Node newNode = null;
......@@ -1100,7 +1102,6 @@ public class PubSubEngine {
elem.addElement("create").addAttribute("node", newNode.getNodeID());
}
router.route(reply);
// TODO Trigger notifications that parent has new child
}
}
catch (NotAcceptableException e) {
......@@ -1281,6 +1282,52 @@ public class PubSubEngine {
node.sendAffiliatedEntities(iq);
}
/**
* Terminates the subscription of the specified entity to all nodes hosted at the service.
* The affiliation with the node will be removed if the entity was not a node owner or
* publisher.
*
* @param user the entity that no longer exists.
*/
private void cancelAllSubscriptions(JID user) {
for (Node node : service.getNodes()) {
NodeAffiliate affiliate = node.getAffiliate(user);
if (affiliate == null) {
continue;
}
for (NodeSubscription subscription : affiliate.getSubscriptions()) {
// Cancel subscription
node.cancelSubscription(subscription);
}
}
}
private void processAuthorizationAnswer(DataForm authForm, Message message) {
String nodeID = authForm.getField("pubsub#node").getValues().get(0);
String subID = authForm.getField("pubsub#subid").getValues().get(0);
String allow = authForm.getField("pubsub#allow").getValues().get(0);
boolean approved;
if ("1".equals(allow) || "true".equals(allow)) {
approved = true;
}
else if ("0".equals(allow) || "false".equals(allow)) {
approved = false;
}
else {
// Unknown allow value. Ignore completed form
Log.warn("Invalid allow value in completed authorization form: " + message.toXML());
return;
}
// Approve or cancel the pending subscription to the node
Node node = service.getNode(nodeID);
if (node != null) {
NodeSubscription subscription = node.getSubscription(subID);
if (subscription != null) {
node.approveSubscription(subscription, approved);
}
}
}
/**
* Generate a conflict packet to indicate that the nickname being requested/used is already in
* use by another user.
......@@ -1315,7 +1362,7 @@ public class PubSubEngine {
*/
private DataForm getSentConfigurationForm(Element configureElement) {
DataForm completedForm = null;
FormField formField = null;
FormField formField;
Element formElement = configureElement.element(QName.get("x", "jabber:x:data"));
if (formElement != null) {
completedForm = new DataForm(formElement);
......@@ -1360,7 +1407,7 @@ public class PubSubEngine {
// Stop te maintenance processes
timer.cancel();
// Delete from the database items contained in the itemsToDelete queue
PublishedItem entry = null;
PublishedItem entry;
while (!itemsToDelete.isEmpty()) {
entry = itemsToDelete.poll();
if (entry != null) {
......@@ -1446,8 +1493,8 @@ public class PubSubEngine {
private class PublishedItemTask extends TimerTask {
public void run() {
try {
PublishedItem entry = null;
boolean success = false;
PublishedItem entry;
boolean success;
// Delete from the database items contained in the itemsToDelete queue
for (int index = 0; index <= items_batch_size && !itemsToDelete.isEmpty(); index++) {
entry = itemsToDelete.poll();
......
......@@ -103,9 +103,13 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
/**
* Keep a registry of the presence's show value of users that subscribed to a node of
* the pubsub service. The Map will have a value only for only users.
* the pubsub service and for which the node only delivers notifications for online users
* or node subscriptions deliver events based on the user presence show value. Offline
* users will not have an entry in the map. Note: Key-> bare JID and Value-> Map whose key
* is full JID of connected resource and value is show value of the last received presence.
*/
private Map<JID, String> presences = new ConcurrentHashMap<JID, String>();
private Map<String, Map<String, String>> barePresences =
new ConcurrentHashMap<String, Map<String, String>>();
public PubSubModule() {
super("Publish Subscribe Service");
......@@ -216,10 +220,40 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
return collectionDefaultConfiguration;
}
public String getShowPresence(JID subscriber) {
// TODO Implement subscribe to presence and update registry of show values.
// TODO Remove presence subscription when user removed his subscription or the node was deleted.
return presences.get(subscriber);
public Collection<String> getShowPresences(JID subscriber) {
Map<String, String> fullPresences = barePresences.get(subscriber.toBareJID());
if (fullPresences == null) {
// User is offline so return empty list
return Collections.emptyList();
}
if (subscriber.getResource() == null) {
// Subscriber used bared JID so return show value of all connected resources
return fullPresences.values();
}
else {
// Look for the show value using the full JID
String show = fullPresences.get(subscriber.toString());
if (show == null) {
// User at the specified resource is offline so return empty list
return Collections.emptyList();
}
// User is connected at specified resource so answer list with presence show value
return Arrays.asList(show);
}
}
public void presenceSubscriptionNotRequired(Node node, JID user) {
// TODO Implement this
}
public void presenceSubscriptionRequired(Node node, JID user) {
Map<String, String> fullPresences = barePresences.get(user.toString());
if (fullPresences == null || fullPresences.isEmpty()) {
Presence subscription = new Presence(Presence.Type.subscribe);
subscription.setTo(user);
subscription.setFrom(getAddress());
send(subscription);
}
}
public PubSubEngine getPubSubEngine() {
......@@ -251,7 +285,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
sysadmins.add(userJID.trim().toLowerCase());
// Update the config.
String[] jids = new String[sysadmins.size()];
jids = (String[])sysadmins.toArray(jids);
jids = sysadmins.toArray(jids);
JiveGlobals.setProperty("xmpp.pubsub.sysadmin.jid", fromArray(jids));
}
......@@ -259,7 +293,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
sysadmins.remove(userJID.trim().toLowerCase());
// Update the config.
String[] jids = new String[sysadmins.size()];
jids = (String[])sysadmins.toArray(jids);
jids = sysadmins.toArray(jids);
JiveGlobals.setProperty("xmpp.pubsub.sysadmin.jid", fromArray(jids));
}
......@@ -277,7 +311,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
allowedToCreate.add(userJID.trim().toLowerCase());
// Update the config.
String[] jids = new String[allowedToCreate.size()];
jids = (String[])allowedToCreate.toArray(jids);
jids = allowedToCreate.toArray(jids);
JiveGlobals.setProperty("xmpp.pubsub.create.jid", fromArray(jids));
}
......@@ -286,7 +320,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
allowedToCreate.remove(userJID.trim().toLowerCase());
// Update the config.
String[] jids = new String[allowedToCreate.size()];
jids = (String[])allowedToCreate.toArray(jids);
jids = allowedToCreate.toArray(jids);
JiveGlobals.setProperty("xmpp.pubsub.create.jid", fromArray(jids));
}
......@@ -408,6 +442,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
super.start();
// Add the route to this service
routingTable.addRoute(getAddress(), this);
// TODO Probe presences of users that this service has subscribed to
ArrayList<String> params = new ArrayList<String>();
params.clear();
params.add(getServiceDomain());
......@@ -576,8 +611,8 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
}
public boolean hasInfo(String name, String node, JID senderJID) {
if (name == null && node == node) {
// We always have info about the MUC service
if (name == null && node == null) {
// We always have info about the Pubsub service
return true;
}
else if (name == null && node != null) {
......@@ -622,7 +657,7 @@ public class PubSubModule extends BasicModule implements ServerItemsProvider, Di
else {
// This is a leaf node so answer the published items which exist on the service
Element item;
for (PublishedItem publishedItem : ((LeafNode) pubNode).getPublishedItems()) {
for (PublishedItem publishedItem : pubNode.getPublishedItems()) {
item = DocumentHelper.createElement("item");
item.addAttribute("jid", serviceDomain);
item.addAttribute("name", publishedItem.getID());
......
......@@ -520,6 +520,11 @@ public class PubSubPersistenceManager {
String subID = rs.getString(2);
JID subscriber = new JID(rs.getString(3));
JID owner = new JID(rs.getString(4));
if (node.getAffiliate(owner) == null) {
Log.warn("Subscription found for a non-existent affiliate: " + owner +
" in node: " + nodeID);
return;
}
NodeSubscription.State state = NodeSubscription.State.valueOf(rs.getString(5));
NodeSubscription subscription =
new NodeSubscription(service, node, owner, subscriber, state, subID);
......@@ -602,15 +607,6 @@ public class PubSubPersistenceManager {
pstmt.setString(4, affiliate.getAffiliation().name());
pstmt.executeUpdate();
}
else {
if (NodeAffiliate.Affiliation.none == affiliate.getAffiliation()) {
// Remove the affiliate from the table of node affiliates
pstmt = con.prepareStatement(DELETE_AFFILIATION);
pstmt.setString(1, service.getServiceID());
pstmt.setString(2, node.getNodeID());
pstmt.setString(3, affiliate.getJID().toString());
pstmt.executeUpdate();
}
else {
// Update the affiliate's data in the backend store
pstmt = con.prepareStatement(UPDATE_AFFILIATION);
......@@ -621,7 +617,6 @@ public class PubSubPersistenceManager {
pstmt.executeUpdate();
}
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
......
......@@ -173,15 +173,17 @@ public interface PubSubService {
DefaultNodeConfiguration getDefaultNodeConfiguration(boolean leafType);
/**
* Returns the show value of the last know presence of the specified subscriber. If the user
* is offline then a <tt>null</tt> value is returned. Available show status is represented
* Returns the show values of the last know presence of all connected resources of the
* specified subscriber. When the subscriber JID is a bare JID then the answered collection
* will have many entries one for each connected resource. Moreover, if the user
* is offline then an empty collectin is returned. Available show status is represented
* by a <tt>online</tt> value. The rest of the possible show values as defined in RFC 3921.
*
* @param subscriber the JID of the subscriber. This is not the JID of the affiliate.
* @return null when offline, online when user if available or show values as defined
* in RFC 3921.
* @return an empty collection when offline. Otherwise, a collection with the show value
* of each connected resource.
*/
String getShowPresence(JID subscriber);
Collection<String> getShowPresences(JID subscriber);
/**
* Returns the pubsub engine responsible for handling packets sent to the pub-sub service.
......@@ -191,4 +193,23 @@ public interface PubSubService {
* @return the pubsub engine responsible for handling packets sent to the pub-sub service.
*/
PubSubEngine getPubSubEngine();
/**
* Requests the pubsub service to subscribe to the presence of the user. If the service
* has already subscribed to the user's presence then do nothing.
*
* @param node the node that originated the subscription request.
* @param user the JID of the affiliate to subscribe to his presence.
*/
void presenceSubscriptionRequired(Node node, JID user);
/**
* Requests the pubsub service to unsubscribe from the presence of the user. If the service
* was not subscribed to the user's presence or any node still requires to be subscribed to
* the user presence then do nothing.
*
* @param node the node that originated the unsubscription request.
* @param user the JID of the affiliate to unsubscribe from his presence.
*/
void presenceSubscriptionNotRequired(Node node, JID user);
}
......@@ -35,6 +35,10 @@ public class AuthorizeAccess extends AccessModel {
}
public boolean canAccessItems(Node node, JID owner, JID subscriber) {
// Let node owners and sysadmins always get node items
if (node.isAdmin(owner)) {
return true;
}
NodeAffiliate nodeAffiliate = node.getAffiliate(owner);
if (nodeAffiliate == null) {
// This is an unknown entity to the node so deny access
......
......@@ -34,8 +34,16 @@ public class PresenceAccess extends AccessModel {
}
public boolean canSubscribe(Node node, JID owner, JID subscriber) {
// Let node owners and sysadmins always subcribe to the node
if (node.isAdmin(owner)) {
return true;
}
// Get the only owner of the node
JID nodeOwner = node.getOwners().iterator().next();
// Give access to the owner of the roster :)
if (nodeOwner.toBareJID().equals(owner.toBareJID())) {
return true;
}
// Get the roster of the node owner
XMPPServer server = XMPPServer.getInstance();
// Check that the node owner is a local user
......
......@@ -38,8 +38,16 @@ public class RosterAccess extends AccessModel {
}
public boolean canSubscribe(Node node, JID owner, JID subscriber) {
// Let node owners and sysadmins always subcribe to the node
if (node.isAdmin(owner)) {
return true;
}
// Get the only owner of the node
JID nodeOwner = node.getOwners().iterator().next();
// Give access to the owner of the roster :)
if (nodeOwner.toBareJID().equals(owner.toBareJID())) {
return true;
}
// Get the roster of the node owner
XMPPServer server = XMPPServer.getInstance();
// Check that the node owner is a local user
......@@ -64,6 +72,7 @@ public class RosterAccess extends AccessModel {
}
}
catch (UserNotFoundException e) {
// Do nothing
}
}
else {
......
......@@ -30,6 +30,10 @@ public class WhitelistAccess extends AccessModel {
}
public boolean canSubscribe(Node node, JID owner, JID subscriber) {
// Let node owners and sysadmins always subcribe to the node
if (node.isAdmin(owner)) {
return true;
}
// User is in the whitelist if he has an affiliation and it is not of type outcast
NodeAffiliate nodeAffiliate = node.getAffiliate(owner);
return nodeAffiliate != null &&
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment