/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.storageprovider; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.apache.vysper.xml.fragment.XMLElement; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.ItemVisitor; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.MemberAffiliationVisitor; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.PubSubAffiliation; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.SubscriberVisitor; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.model.LastOwnerResignedException; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.model.LeafNode; import org.apache.vysper.xmpp.modules.extension.xep0060_pubsub.model.PayloadItem; /** * This storage provider keeps all objects in memory and looses its content when * removed from memory. This is the default storage provider for leaf nodes. * * @author The Apache MINA Project (http://mina.apache.org) */ public class LeafNodeInMemoryStorageProvider implements LeafNodeStorageProvider { // The node owners protected Map<String, Map<Entity, PubSubAffiliation>> nodeAffiliations; // stores subscribers to a node, access via subid protected Map<String, Map<String, Entity>> nodeSubscribers; // stores messages to a node, access via itemid protected Map<String, Map<String, PayloadItem>> nodeMessages; /** * Initialize the storage maps. */ public LeafNodeInMemoryStorageProvider() { this.nodeSubscribers = new TreeMap<String, Map<String, Entity>>(); this.nodeMessages = new TreeMap<String, Map<String, PayloadItem>>(); this.nodeAffiliations = new TreeMap<String, Map<Entity, PubSubAffiliation>>(); } /** * Add a subscriber with given subID. */ public void addSubscriber(String nodeName, String subscriptionID, Entity subscriber) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); subscribers.put(subscriptionID, subscriber); } /** * Check if a subscriber is already known. */ public boolean containsSubscriber(String nodeName, Entity subscriber) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.containsValue(subscriber); } /** * Check if a subscriptionId is already known. */ public boolean containsSubscriber(String nodeName, String subscriptionId) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.containsKey(subscriptionId); } /** * Retrieve a subscriber via its subsriptionId. */ public Entity getSubscriber(String nodeName, String subscriptionId) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.get(subscriptionId); } /** * Remove a subscriber via its subscriptionId. */ public boolean removeSubscription(String nodeName, String subscriptionId) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.remove(subscriptionId) != null; } /** * Remove a subscriber via its JID. This removes all subscriptions of the JID. */ public boolean removeSubscriber(String nodeName, Entity subscriber) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.values().remove(subscriber); } /** * Count how often a given subscriber is subscribed. */ public int countSubscriptions(String nodeName, Entity subscriber) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); int count = 0; for (Entity sub : subscribers.values()) { if (subscriber.equals(sub)) { ++count; } } return count; } /** * Count how many subscriptions this node has. */ public int countSubscriptions(String nodeName) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); return subscribers.size(); } /** * Add a message to the storage. */ public void addMessage(Entity publisher, String nodeName, String itemID, XMLElement payload) { Map<String, PayloadItem> messages = nodeMessages.get(nodeName); messages.put(itemID, new PayloadItem(publisher, payload, itemID)); } /** * Accept method (see visitor pattern) to visit all subscribers of this node. */ public void acceptForEachSubscriber(String nodeName, SubscriberVisitor subscriberVisitor) { Map<String, Entity> subscribers = nodeSubscribers.get(nodeName); for (String subID : subscribers.keySet()) { subscriberVisitor.visit(nodeName, subID, subscribers.get(subID)); } } /** * The in-memory storage provider does not need initialization beyond creating the objects. */ public void initialize() { // empty } /** * Go through each message and call visit of the visitor. */ public void acceptForEachItem(String nodeName, ItemVisitor iv) { Map<String, PayloadItem> messages = nodeMessages.get(nodeName); for (String itemID : messages.keySet()) { iv.visit(itemID, messages.get(itemID)); } } /** * Initialize the node with the storage. */ public void initialize(LeafNode leafNode) { nodeMessages.put(leafNode.getName(), new TreeMap<String, PayloadItem>()); nodeSubscribers.put(leafNode.getName(), new TreeMap<String, Entity>()); nodeAffiliations.put(leafNode.getName(), new HashMap<Entity, PubSubAffiliation>()); } /** * Remove the specified node from the storage. */ public void delete(String name) { nodeMessages.remove(name); nodeSubscribers.remove(name); nodeAffiliations.remove(name); } /** * Add the entity to the owner list of the given node. * The owner is stored as bare JID. */ public void setAffiliation(String nodeName, Entity entity, PubSubAffiliation affiliation) throws LastOwnerResignedException { Map<Entity, PubSubAffiliation> affils = this.nodeAffiliations.get(nodeName); Entity bareJID = entity.getBareJID(); if (getAffiliation(nodeName, bareJID).equals(PubSubAffiliation.OWNER) && !affiliation.equals(PubSubAffiliation.OWNER) && countAffiliations(nodeName, PubSubAffiliation.OWNER) == 1) { throw new LastOwnerResignedException(bareJID.getFullQualifiedName() + " tried to resign from " + nodeName); } if (affiliation.equals(PubSubAffiliation.NONE)) { affils.remove(bareJID); // NONE affiliations are not stored. } else { affils.put(bareJID, affiliation); } } /** * Calculates how many users with the given affiliation are present for this node. * @param nodeName the node to check * @param affiliation to count * @return the number of owners. */ private int countAffiliations(String nodeName, PubSubAffiliation affiliation) { Map<Entity, PubSubAffiliation> affils = this.nodeAffiliations.get(nodeName); int i = 0; for (PubSubAffiliation a : affils.values()) { if (a.equals(affiliation)) ++i; } return i; } /** * Returns the affiliation of the entity to the node. Only the bare JID will be compared. */ public PubSubAffiliation getAffiliation(String nodeName, Entity entity) { PubSubAffiliation psa = this.nodeAffiliations.get(nodeName).get(entity.getBareJID()); return psa != null ? psa : PubSubAffiliation.NONE; // NONE if there is no affiliation known. } /** * Call the visitor with the each member JID and its associated affiliation. */ public void acceptForEachMemberAffiliation(String name, MemberAffiliationVisitor mav) { Map<Entity, PubSubAffiliation> affils = this.nodeAffiliations.get(name); for (Entity jid : affils.keySet()) { PubSubAffiliation affil = affils.get(jid); mav.visit(jid, affil); } } }