NodeAffiliate.java 13.2 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
Matt Tucker's avatar
Matt Tucker committed
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * 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.
Matt Tucker's avatar
Matt Tucker committed
19 20
 */

21
package org.jivesoftware.openfire.pubsub;
Matt Tucker's avatar
Matt Tucker committed
22

23 24 25 26 27 28
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Matt Tucker's avatar
Matt Tucker committed
29
import org.dom4j.Element;
Matt Tucker's avatar
Matt Tucker committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;

/**
 * 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;

47
    public NodeAffiliate(Node node, JID jid) {
Matt Tucker's avatar
Matt Tucker committed
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
        this.node = node;
        this.jid = jid;
    }

    public Node getNode() {
        return node;
    }

    public JID getJID() {
        return jid;
    }

    public Affiliation getAffiliation() {
        return affiliation;
    }

64
    public void setAffiliation(Affiliation affiliation) {
Matt Tucker's avatar
Matt Tucker committed
65 66 67 68 69 70 71 72 73 74 75 76
        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
77 78 79 80 81 82 83 84 85 86 87
    /**
     * 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.
88
     * @param leafNode the leaf node where the items where published.
Matt Tucker's avatar
Matt Tucker committed
89 90
     * @param publishedItems the list of items that were published. Could be an empty list.
     */
91
    void sendPublishedNotifications(Message notification, Element event, LeafNode leafNode,
Matt Tucker's avatar
Matt Tucker committed
92 93 94 95
            List<PublishedItem> publishedItems) {

        if (!publishedItems.isEmpty()) {
            Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
96
                    getItemsBySubscriptions(leafNode, publishedItems);
Matt Tucker's avatar
Matt Tucker committed
97 98 99 100 101

            // 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
102
                items.addAttribute("node", getNode().getNodeID());
Matt Tucker's avatar
Matt Tucker committed
103
                for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
104 105 106 107 108
                    // FIXME: This was added for compatibility with PEP supporting clients.
                    //        Alternate solution needed when XEP-0163 version > 1.0 is released.
                    //
                    // If the node ID looks like a JID, replace it with the published item's node ID.
                    if (getNode().getNodeID().indexOf("@") >= 0) {
109
                        items.addAttribute("node", publishedItem.getNodeID());                        
110 111
                    }

Matt Tucker's avatar
Matt Tucker committed
112 113
                    // Add item information to the event notification
                    Element item = items.addElement("item");
114
                    if (leafNode.isItemRequired()) {
Matt Tucker's avatar
Matt Tucker committed
115 116
                        item.addAttribute("id", publishedItem.getID());
                    }
117
                    if (leafNode.isPayloadDelivered()) {
Matt Tucker's avatar
Matt Tucker committed
118 119
                        item.add(publishedItem.getPayload().createCopy());
                    }
120
                    // Add leaf leafNode information if affiliated leafNode and node
Matt Tucker's avatar
Matt Tucker committed
121
                    // where the item was published are different
122 123
                    if (leafNode != getNode()) {
                        item.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
124
                    }
Matt Tucker's avatar
Matt Tucker committed
125 126
                }
                // Send the event notification
127
                sendEventNotification(notification, nodeSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
128 129 130 131 132 133
                // Remove the added items information
                event.remove(items);
            }
        }
        else {
            // Filter affiliate subscriptions and only use approved and configured ones
134
            List<NodeSubscription> affectedSubscriptions = new ArrayList<NodeSubscription>();
Matt Tucker's avatar
Matt Tucker committed
135
            for (NodeSubscription subscription : getSubscriptions()) {
136
                if (subscription.canSendPublicationEvent(leafNode, null)) {
Matt Tucker's avatar
Matt Tucker committed
137 138 139 140 141
                    affectedSubscriptions.add(subscription);
                }
            }
            // Add item information to the event notification
            Element items = event.addElement("items");
142
            items.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
143
            // Send the event notification
144
            sendEventNotification(notification, affectedSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
            // 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.
161
     * @param leafNode the leaf node where the items where deleted from.
Matt Tucker's avatar
Matt Tucker committed
162 163
     * @param publishedItems the list of items that were deleted.
     */
164
    void sendDeletionNotifications(Message notification, Element event, LeafNode leafNode,
Matt Tucker's avatar
Matt Tucker committed
165 166 167 168
            List<PublishedItem> publishedItems) {

        if (!publishedItems.isEmpty()) {
            Map<List<NodeSubscription>, List<PublishedItem>> itemsBySubs =
169
                    getItemsBySubscriptions(leafNode, publishedItems);
Matt Tucker's avatar
Matt Tucker committed
170 171 172 173 174

            // 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");
175
                items.addAttribute("node", leafNode.getNodeID());
Matt Tucker's avatar
Matt Tucker committed
176 177 178
                for (PublishedItem publishedItem : itemsBySubs.get(nodeSubscriptions)) {
                    // Add retract information to the event notification
                    Element item = items.addElement("retract");
179
                    if (leafNode.isItemRequired()) {
Matt Tucker's avatar
Matt Tucker committed
180 181 182 183
                        item.addAttribute("id", publishedItem.getID());
                    }
                }
                // Send the event notification
184
                sendEventNotification(notification, nodeSubscriptions);
Matt Tucker's avatar
Matt Tucker committed
185 186 187 188 189 190
                // Remove the added items information
                event.remove(items);
            }
        }
    }

Matt Tucker's avatar
Matt Tucker committed
191 192 193 194 195
    /**
     * 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
196 197 198
     * Event notifications may include notifications of new published items or of items that
     * were deleted.<p>
     *
Matt Tucker's avatar
Matt Tucker committed
199 200 201 202 203
     * 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
204 205
     * @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
206
     */
207
    private void sendEventNotification(Message notification,
Matt Tucker's avatar
Matt Tucker committed
208
            List<NodeSubscription> notifySubscriptions) {
Matt Tucker's avatar
Matt Tucker committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
        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 {
229
            // Affiliate should have at most one subscription per unique JID
Matt Tucker's avatar
Matt Tucker committed
230
            if (!notifySubscriptions.isEmpty()) {
231 232 233 234
            	List<JID> subs = new ArrayList<JID>();
            	for(NodeSubscription subscription: notifySubscriptions) {
            		JID sub = subscription.getJID();
            		if (!subs.contains(sub)) {
235
            			node.sendEventNotification(sub, notification, null);
236 237 238
            			subs.add(sub);
            		}
            	}
Matt Tucker's avatar
Matt Tucker committed
239 240 241 242
            }
        }
    }

243 244
    private Map<List<NodeSubscription>, List<PublishedItem>> getItemsBySubscriptions(
            LeafNode leafNode, List<PublishedItem> publishedItems) {
Matt Tucker's avatar
Matt Tucker committed
245 246 247 248 249 250 251 252
        // 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) {
253
                if (subscription.canSendPublicationEvent(leafNode, publishedItem)) {
Matt Tucker's avatar
Matt Tucker committed
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
                    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) {
271 272 273
            	List<PublishedItem> items = new ArrayList<PublishedItem>(publishedItems.size());
            	items.add(publishedItem);
                itemsBySubs.put(subsByItem.get(publishedItem), items);
Matt Tucker's avatar
Matt Tucker committed
274 275 276 277 278 279 280 281
            }
            else {
                affectedSubscriptions.add(publishedItem);
            }
        }
        return itemsBySubs;
    }

282 283
    @Override
	public String toString() {
Matt Tucker's avatar
Matt Tucker committed
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
        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.
         */
308
        outcast
Matt Tucker's avatar
Matt Tucker committed
309 310
    }
}