NodeAffiliate.java 11.9 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5 6 7 8 9 10 11 12 13
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2006 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package org.jivesoftware.wildfire.pubsub;

Matt Tucker's avatar
Matt Tucker committed
14
import org.dom4j.Element;
Matt Tucker's avatar
Matt Tucker committed
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;

import java.util.*;

/**
 * A NodeAffiliate keeps information about the affiliation of an entity with a node. Possible
 * affiliations are: owner, publisher, none or outcast. All except for outcast affiliations
 * may have a {@link NodeSubscription} with the node.
 *
 * @author Matt Tucker
 */
public class NodeAffiliate {

    private JID jid;
    private Node node;

    private Affiliation affiliation;

    NodeAffiliate(Node node, JID jid) {
        this.node = node;
        this.jid = jid;
    }

    public Node getNode() {
        return node;
    }

    public JID getJID() {
        return jid;
    }

    public Affiliation getAffiliation() {
        return affiliation;
    }

    void setAffiliation(Affiliation affiliation) {
        this.affiliation = affiliation;
    }

    /**
     * Returns the list of subscriptions of the affiliate in the node.
     *
     * @return the list of subscriptions of the affiliate in the node.
     */
    public Collection<NodeSubscription> getSubscriptions() {
        return node.getSubscriptions(jid);
    }

Matt Tucker's avatar
Matt Tucker committed
64 65 66 67 68 69 70 71 72 73 74
    /**
     * Sends an event notification for the published items to the affiliate. The event
     * notification may contain zero, one or many published items based on the items
     * included in the original publication. If the affiliate has many subscriptions and
     * many items were published then the affiliate will get a notification for each set
     * of items that affected the same subscriptions.
     *
     * @param notification the message to sent to the subscribers. The message will be completed
     *        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.
75
     * @param leafNode the leaf node where the items where published.
Matt Tucker's avatar
Matt Tucker committed
76 77
     * @param publishedItems the list of items that were published. Could be an empty list.
     */
78
    void sendPublishedNotifications(Message notification, Element event, LeafNode leafNode,
Matt Tucker's avatar
Matt Tucker committed
79 80 81 82
            List<PublishedItem> publishedItems) {

        if (!publishedItems.isEmpty()) {
            Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
83
                    getItemsBySubscriptions(leafNode, publishedItems);
Matt Tucker's avatar
Matt Tucker committed
84 85 86 87 88

            // 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");
Matt Tucker's avatar
Matt Tucker committed
89
                items.addAttribute("node", getNode().getNodeID());
Matt Tucker's avatar
Matt Tucker committed
90 91 92
                for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
                    // Add item information to the event notification
                    Element item = items.addElement("item");
93
                    if (leafNode.isItemRequired()) {
Matt Tucker's avatar
Matt Tucker committed
94 95
                        item.addAttribute("id", publishedItem.getID());
                    }
96
                    if (leafNode.isPayloadDelivered()) {
Matt Tucker's avatar
Matt Tucker committed
97 98
                        item.add(publishedItem.getPayload().createCopy());
                    }
99
                    // Add leaf leafNode information if affiliated leafNode and node
Matt Tucker's avatar
Matt Tucker committed
100
                    // where the item was published are different
101 102
                    if (leafNode != getNode()) {
                        item.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
103
                    }
Matt Tucker's avatar
Matt Tucker committed
104 105
                }
                // Send the event notification
106
                sendEventNotification(notification, nodeSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
107 108 109 110 111 112
                // Remove the added items information
                event.remove(items);
            }
        }
        else {
            // Filter affiliate subscriptions and only use approved and configured ones
113
            List<NodeSubscription> affectedSubscriptions = new ArrayList<NodeSubscription>();
Matt Tucker's avatar
Matt Tucker committed
114
            for (NodeSubscription subscription : getSubscriptions()) {
115
                if (subscription.canSendPublicationEvent(leafNode, null)) {
Matt Tucker's avatar
Matt Tucker committed
116 117 118 119 120
                    affectedSubscriptions.add(subscription);
                }
            }
            // Add item information to the event notification
            Element items = event.addElement("items");
121
            items.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
122
            // Send the event notification
123
            sendEventNotification(notification, affectedSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
            // Remove the added items information
            event.remove(items);
        }
    }

    /**
     * Sends an event notification to the affiliate for the deleted items. The event
     * notification may contain one or many published items based on the items included
     * in the original publication. If the affiliate has many subscriptions and many
     * items were deleted then the affiliate will get a notification for each set
     * of items that affected the same subscriptions.
     *
     * @param notification the message to sent to the subscribers. The message will be completed
     *        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.
140
     * @param leafNode the leaf node where the items where deleted from.
Matt Tucker's avatar
Matt Tucker committed
141 142
     * @param publishedItems the list of items that were deleted.
     */
143
    void sendDeletionNotifications(Message notification, Element event, LeafNode leafNode,
Matt Tucker's avatar
Matt Tucker committed
144 145 146 147
            List<PublishedItem> publishedItems) {

        if (!publishedItems.isEmpty()) {
            Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
148
                    getItemsBySubscriptions(leafNode, publishedItems);
Matt Tucker's avatar
Matt Tucker committed
149 150 151 152 153

            // 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");
154
                items.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
155 156 157
                for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
                    // Add retract information to the event notification
                    Element item = items.addElement("retract");
158
                    if (leafNode.isItemRequired()) {
Matt Tucker's avatar
Matt Tucker committed
159 160 161 162
                        item.addAttribute("id", publishedItem.getID());
                    }
                }
                // Send the event notification
163
                sendEventNotification(notification, nodeSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
164 165 166 167 168 169
                // Remove the added items information
                event.remove(items);
            }
        }
    }

Matt Tucker's avatar
Matt Tucker committed
170 171 172 173 174
    /**
     * Sends an event notification to each affected subscription of the affiliate. If the owner
     * has many subscriptions from the same full JID then a single notification is going to be
     * sent including a detail of the subscription IDs for which the notification is being sent.<p>
     *
Matt Tucker's avatar
Matt Tucker committed
175 176 177
     * Event notifications may include notifications of new published items or of items that
     * were deleted.<p>
     *
Matt Tucker's avatar
Matt Tucker committed
178 179 180 181 182
     * The original publication to the node may or may not contain a {@link PublishedItem}. The
     * subscriptions of the affiliation will be filtered based on the published item (if one was
     * specified), the subscription status and originating node.
     *
     * @param notification the message to send containing the event notification.
Matt Tucker's avatar
Matt Tucker committed
183 184
     * @param notifySubscriptions list of subscriptions that were affected and are going to be
     *        included in the notification message. The list should not be empty.
Matt Tucker's avatar
Matt Tucker committed
185
     */
186
    private void sendEventNotification(Message notification,
Matt Tucker's avatar
Matt Tucker committed
187
            List<NodeSubscription> notifySubscriptions) {
Matt Tucker's avatar
Matt Tucker committed
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
        if (node.isMultipleSubscriptionsEnabled()) {
            // Group subscriptions with the same subscriber JID
            Map<JID, Collection<String>> groupedSubs = new HashMap<JID, Collection<String>>();
            for (NodeSubscription subscription : notifySubscriptions) {
                Collection<String> subIDs = groupedSubs.get(subscription.getJID());
                if (subIDs == null) {
                    subIDs = new ArrayList<String>();
                    groupedSubs.put(subscription.getJID(), subIDs);
                }
                subIDs.add(subscription.getID());
            }
            // Send an event notification to each subscriber with a different JID
            for (JID subscriberJID : groupedSubs.keySet()) {
                // Get ID of affected subscriptions
                Collection<String> subIDs = groupedSubs.get(subscriberJID);
                // Send the notification to the subscriber
                node.sendEventNotification(subscriberJID, notification, subIDs);
            }
        }
        else {
            // Affiliate should have at most one subscription so send the notification to
            // the subscriber
            if (!notifySubscriptions.isEmpty()) {
                NodeSubscription subscription = notifySubscriptions.get(0);
                node.sendEventNotification(subscription.getJID(), notification, null);
            }
        }
    }

217 218
    private Map<List<NodeSubscription>, List<PublishedItem>> getItemsBySubscriptions(
            LeafNode leafNode, List<PublishedItem> publishedItems) {
Matt Tucker's avatar
Matt Tucker committed
219 220 221 222 223 224 225 226
        // Identify which subscriptions can receive each item
        Map<PublishedItem, List<NodeSubscription>> subsByItem =
                new HashMap<PublishedItem, List<NodeSubscription>>();

        // Filter affiliate subscriptions and only use approved and configured ones
        Collection<NodeSubscription> subscriptions = getSubscriptions();
        for (PublishedItem publishedItem : publishedItems) {
            for (NodeSubscription subscription : subscriptions) {
227
                if (subscription.canSendPublicationEvent(leafNode, publishedItem)) {
Matt Tucker's avatar
Matt Tucker committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
                    List<NodeSubscription> nodeSubscriptions = subsByItem.get(publishedItem);
                    if (nodeSubscriptions == null) {
                        nodeSubscriptions = new ArrayList<NodeSubscription>();
                        subsByItem.put(publishedItem, nodeSubscriptions);
                    }
                    nodeSubscriptions.add(subscription);
                }
            }
        }

        // Identify which items should be sent together to the same subscriptions
        Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
                new HashMap<List<NodeSubscription>, List<PublishedItem>>();
        List<PublishedItem> affectedSubscriptions;
        for (PublishedItem publishedItem : subsByItem.keySet()) {
            affectedSubscriptions = itemsBySubs.get(subsByItem.get(publishedItem));
            if (affectedSubscriptions == null) {
                itemsBySubs.put(subsByItem.get(publishedItem), Arrays.asList(publishedItem));
            }
            else {
                affectedSubscriptions.add(publishedItem);
            }
        }
        return itemsBySubs;
    }

Matt Tucker's avatar
Matt Tucker committed
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    public String toString() {
        return super.toString() + " - JID: " + getJID() + " - Affiliation: " +
                getAffiliation().name();
    }

    /**
     * Affiliation with a node defines user permissions.
     */
    public static enum Affiliation {

        /**
         * An owner can publish, delete and purge items as well as configure and delete the node.
         */
        owner,
        /**
         * A publisher can subscribe and publish items to the node.
         */
        publisher,
        /**
         * A user with no affiliation can susbcribe to the node.
         */
        none,
        /**
         * Outcast users are not allowed to subscribe to the node.
         */
279
        outcast
Matt Tucker's avatar
Matt Tucker committed
280 281
    }
}