/** * Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET * (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije * informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE * COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM ISRAEL * SCIENCE AND TECHNOLOGY LTD (IBM), INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA * PERIORISMENIS EFTHINIS (AMITEC), TELECOM ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD * (NEC)) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @author Joao M. Goncalves (PTIN) * * TODO */ package org.societies.comm.xmpp.pubsub.impl; import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.criterion.Restrictions; import org.jabber.protocol.pubsub.Create; import org.jabber.protocol.pubsub.Item; import org.jabber.protocol.pubsub.Items; import org.jabber.protocol.pubsub.Pubsub; import org.jabber.protocol.pubsub.Subscription; import org.jabber.protocol.pubsub.errors.Unsupported; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.societies.api.comm.xmpp.datatypes.Stanza; import org.societies.api.comm.xmpp.exceptions.XMPPError; import org.societies.api.comm.xmpp.datatypes.StanzaError; import org.societies.api.comm.xmpp.interfaces.ICommManager; import org.societies.api.identity.IIdentity; import org.societies.api.identity.IIdentityManager; import org.societies.api.identity.InvalidFormatException; import org.societies.comm.xmpp.pubsub.PubsubService; import org.societies.comm.xmpp.pubsub.model.PubsubNodeDAO; import org.societies.comm.xmpp.pubsub.model.PubsubServiceDAO; // TODO public class PubsubServiceImpl implements PubsubService { private static Logger LOG = LoggerFactory .getLogger(PubsubServiceImpl.class); // PubSub Errors private static final Object ERROR_SUBID_REQUIRED = null; private static final Object ERROR_NOT_SUBSCRIBED = null; private static final Object ERROR_INVALID_SUBID = null; private static final Object ERROR_NODEID_REQUIRED = null; private static final Object ERROR_ITEM_REQUIRED = null; private static final Object ERROR_INVALID_JID = null; /* static { org.jabber.protocol.pubsub.errors.ObjectFactory errorFactory = new org.jabber.protocol.pubsub.errors.ObjectFactory(); ERROR_SUBID_REQUIRED = errorFactory.createSubidRequired(""); ERROR_NOT_SUBSCRIBED = errorFactory.createNotSubscribed(""); ERROR_INVALID_SUBID = errorFactory.createInvalidSubid(""); ERROR_NODEID_REQUIRED = errorFactory.createNodeidRequired(""); ERROR_ITEM_REQUIRED = errorFactory.createItemRequired(""); ERROR_INVALID_JID = errorFactory.createInvalidJid(""); } */ // PubSub Constants private static final String SUBSCRIPTION_SUBSCRIBED = "subscribed"; private static final String SUBSCRIPTION_UNCONFIGURED = "unconfigured"; // TODO private static final String SUBSCRIPTION_NONE = "none"; private static final String AFFILIATION_OWNER = "owner"; private static final String AFFILIATION_MEMBER = "member"; private static final String AFFILIATION_PUBLISHER = "publisher"; private static final String AFFILIATION_PUBLISH_ONLY = "publish-only"; private static final String AFFILIATION_OUTCAST = "outcast"; // Fields private Map<String, PubsubNode> nodes; private Map<String, String> redirectedNodes; // TODO use! private PubsubEventSender pes; private List<IIdentity> admins; // TODO use! private ICommManager endpoint; private IIdentityManager idm; private SessionFactory sf; // private Session s; private PubsubServiceDAO dao; public PubsubServiceImpl(ICommManager endpoint) { init(endpoint); } private void init(ICommManager endpoint) { nodes = new HashMap<String, PubsubNode>(); redirectedNodes = new HashMap<String, String>(); pes = new PubsubEventSender(endpoint); this.endpoint = endpoint; admins = new ArrayList<IIdentity>(); idm = endpoint.getIdManager(); } public PubsubServiceImpl(ICommManager endpoint, SessionFactory sf) { init(endpoint); this.sf = sf; Session s = sf.openSession(); Transaction tx = null; try { tx = s.beginTransaction(); String pssJid = endpoint.getIdManager().getThisNetworkNode().getJid(); List list = s.createCriteria(PubsubServiceDAO.class).add(Restrictions.like("pubsubServiceEndpoint", pssJid)).list(); if (list.size()>0) { dao = (PubsubServiceDAO) list.get(0); loadFromDAO(); } else { dao = new PubsubServiceDAO(); dao.setPubsubServiceEndpoint(pssJid); s.save(dao); } tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); throw e; } finally { s.close(); } } private void loadFromDAO() { for (PubsubNodeDAO pnDao : dao.getNodes()) { PubsubNode psn = new PubsubNode(pnDao, sf, idm); nodes.put(pnDao.getNodeId(),psn); endpoint.addRootNode(psn); } } private void writeToDAO(PubsubNode node) { if (sf != null) { Session s = sf.openSession(); Transaction tx = null; try { tx = s.beginTransaction(); if (nodes.containsValue(node)) { // add PubsubNodeDAO pnDao = node.enablePersistence(sf, idm); dao.getNodes().add(pnDao); pnDao.setPubsubService(dao); s.save(pnDao); } else { // remove Object o = s.load(PubsubNodeDAO.class, node.getDAO().getHbnId()); s.delete(o); } tx.commit(); } catch (HibernateException e) { if (tx!=null) tx.rollback(); throw e; } finally { s.close(); } } } @Override public Pubsub subscriberSubscribe(Stanza stanza, Pubsub payload) throws XMPPError { IIdentity sender = stanza.getFrom(); IIdentity subscriber; try { subscriber = idm.fromJid(payload.getSubscribe().getJid()); } catch (InvalidFormatException e) { throw new XMPPError(StanzaError.bad_request, null, ERROR_INVALID_JID); } String nodeId = payload.getSubscribe().getNode(); // TODO "The <subscribe/> element SHOULD possess a 'node' attribute"... what happens when it doesn't? // 6.1.3.1 JIDs Do Not Match (match sender and subscriber) if (!sender.equals(subscriber) && !admins.contains(sender)) throw new XMPPError(StanzaError.bad_request, null, ERROR_INVALID_JID); // TODO Access Control PubsubNode node = nodes.get(nodeId); if (node==null) { String redirectUri = redirectedNodes.get(nodeId); // 6.1.3.12 Node Does Not Exist if (redirectUri==null) throw new XMPPError(StanzaError.item_not_found); // 6.1.3.11 Node Has Moved else throw new XMPPError(StanzaError.gone,redirectUri,null); } // New Subscription String subId = node.newSubscription(subscriber); // Build success response Pubsub response = new Pubsub(); Subscription subs = new Subscription(); subs.setJid(subscriber.getJid()); subs.setNode(nodeId); subs.setSubid(subId); subs.setSubscription(SUBSCRIPTION_SUBSCRIBED); response.setSubscription(subs); return response; } @Override public void subscriberUnsubscribe(Stanza stanza, Pubsub payload) throws XMPPError { IIdentity sender = stanza.getFrom(); IIdentity subscriber; try { subscriber = idm.fromJid(payload.getUnsubscribe().getJid()); } catch (InvalidFormatException e) { throw new XMPPError(StanzaError.bad_request, null, ERROR_INVALID_JID); } String nodeId = payload.getUnsubscribe().getNode(); String subId = payload.getUnsubscribe().getSubid(); // 6.2.3.3 Insufficient Privileges (match sender and subscriber) if (!sender.equals(subscriber) && !admins.contains(sender)) throw new XMPPError(StanzaError.forbidden); PubsubNode node = nodes.get(nodeId); // 6.2.3.4 Node Does Not Exist if (node==null) throw new XMPPError(StanzaError.item_not_found); List<String> subIdList = node.getSubscriptions(subscriber); // 6.2.3.2 No Such Subscriber if (subIdList==null) throw new XMPPError(StanzaError.unexpected_request, null, ERROR_NOT_SUBSCRIBED); // 6.2.3.1 No Subscription ID if (subIdList.size()>1 && subId==null) throw new XMPPError(StanzaError.bad_request, null, ERROR_SUBID_REQUIRED); if (subId!=null) { // 6.2.3.5 Bad Subscription ID if (!subIdList.contains(subId)) throw new XMPPError(StanzaError.unexpected_request, null, ERROR_INVALID_SUBID); // Unsubscribe node.unsubscribe(subId); } else node.unsubscribe(subIdList.get(0)); } @Override public Pubsub subscriberOptionsRequest(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 6.3 Unsupported u = new Unsupported(); u.setFeature("subscription-options"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public Pubsub subscriberOptionsSubmission(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 6.3 Unsupported u = new Unsupported(); u.setFeature("subscription-options"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public Pubsub subscriberSubscribeConfigure(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 6.3 Unsupported u = new Unsupported(); u.setFeature("subscription-options"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public Pubsub subscriberDefaultOptions(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 6.4 Unsupported u = new Unsupported(); u.setFeature("subscription-options"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public Pubsub subscriberRetrieve(Stanza stanza, Pubsub payload) throws XMPPError { IIdentity sender = stanza.getFrom(); String nodeId = payload.getItems().getNode(); String subId = payload.getItems().getSubid(); List<Item> itemList = payload.getItems().getItem(); BigInteger maxItems = payload.getItems().getMaxItems(); PubsubNode node = nodes.get(nodeId); // 6.5.9.11 Node Does Not Exist if (node==null) throw new XMPPError(StanzaError.item_not_found); List<String> subIdList = node.getSubscriptions(sender); // 6.5.9.3 Entity Not Subscribed if (subIdList==null) throw new XMPPError(StanzaError.unexpected_request, null, ERROR_NOT_SUBSCRIBED); // 6.5.9.1 Subscription ID Required if (subIdList.size()>1 && subId==null) throw new XMPPError(StanzaError.bad_request, null, ERROR_SUBID_REQUIRED); if (subId!=null) { // 6.5.9.2 Invalid Subscription ID if (!subIdList.contains(subId)) throw new XMPPError(StanzaError.unexpected_request, null, ERROR_INVALID_SUBID); } else subId = subIdList.get(0); // TODO Access Control // Retrieve Items responseItems = new Items(); List<Item> responseItemList = responseItems.getItem(); if (itemList!=null && itemList.size()>0) { // Get specific items for (Item i : itemList) { i.setAny(node.getItemPayload(i.getId())); responseItemList.add(i); } } else { // Get newest items // TODO 6.5.4 Returning Some Items for (String itemId : node.getItemIds()) { Item i = new Item(); i.setId(itemId); Object itemPayload = node.getItemPayload(itemId); i.setAny(itemPayload); responseItemList.add(i); if (maxItems!=null && responseItemList.size()==maxItems.intValue()) break; } } // Build response Pubsub response = new Pubsub(); responseItems.setNode(nodeId); response.setItems(responseItems); return response; } @Override public Pubsub publisherPublish(Stanza stanza, Pubsub payload) throws XMPPError { String nodeId = payload.getPublish().getNode(); Item item = payload.getPublish().getItem(); String sender = stanza.getFrom().getJid(); PubsubNode node = nodes.get(nodeId); // 7.1.3.3 Node Does Not Exist or http://jabber.org/protocol/pubsub#auto-create if (node==null) throw new XMPPError(StanzaError.item_not_found); // TODO http://jabber.org/protocol/pubsub#auto-create if (item==null) { throw new XMPPError(StanzaError.feature_not_implemented); // TODO support for transient nodes (and itemless notifications) } else { // 7.1.3.5 Bad Payload //TODO If the <item/> element contains more than one payload element or the namespace of the root payload element does not match the configured namespace for the node // Publish and Update Item ID for Response String itemId = node.publishItem(item.getId(),item.getAny(),sender); item.setId(itemId); // Build Notifications org.jabber.protocol.pubsub.event.Event event = new org.jabber.protocol.pubsub.event.Event(); org.jabber.protocol.pubsub.event.Items eventItems = new org.jabber.protocol.pubsub.event.Items(); org.jabber.protocol.pubsub.event.Item eventItem = new org.jabber.protocol.pubsub.event.Item(); eventItem.setId(itemId); eventItem.setAny(item.getAny()); eventItems.setNode(nodeId); eventItems.getItem().add(eventItem); event.setItems(eventItems); pes.sendEvent(node.getSubscribers(), event); // TODO 7.1.2.2 Notification Without Payload } // Build Response item.setAny(null); return payload; } @Override public Pubsub publisherPublishOptions(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 7.1.5 Unsupported u = new Unsupported(); u.setFeature("publish-options"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public void publisherDelete(Stanza stanza, Pubsub payload) throws XMPPError { String nodeId = payload.getRetract().getNode(); List<Item> item = payload.getRetract().getItem(); Boolean notify = payload.getRetract().isNotify(); // 7.2.3.3 NodeID Required if (nodeId==null) throw new XMPPError(StanzaError.bad_request, null, ERROR_NODEID_REQUIRED); PubsubNode node = nodes.get(nodeId); // 7.2.3.2 Node Does Not Exist if (node==null) throw new XMPPError(StanzaError.item_not_found); // 7.2.3.4 Item or ItemID Required if (item==null || item.size()!=1 || item.get(0).getId()==null) throw new XMPPError(StanzaError.bad_request, null, ERROR_ITEM_REQUIRED); // TODO Access model // Remove Item String itemId = item.get(0).getId(); node.removeItem(itemId); // 7.2.2.1 Delete And Notify if (notify!=null && notify) { org.jabber.protocol.pubsub.event.Items eventItems = new org.jabber.protocol.pubsub.event.Items(); org.jabber.protocol.pubsub.event.Retract retractEvent = new org.jabber.protocol.pubsub.event.Retract(); retractEvent.setId(itemId); eventItems.setNode(nodeId); eventItems.getRetract().add(retractEvent); pes.sendEvent(node.getSubscribers(), eventItems); // TODO 7.2.2.2 Inclusion of Subscription ID } } @Override public Pubsub ownerCreate(Stanza stanza, Pubsub payload) throws XMPPError { // Support for Support for http://jabber.org/protocol/pubsub#create-nodes IIdentity owner = stanza.getFrom(); String nodeId = payload.getCreate().getNode(); // TODO access model if (nodeId==null) { // Support for http://jabber.org/protocol/pubsub#instant-nodes nodeId = UUID.randomUUID().toString(); while (nodes.containsKey(nodeId)) { nodeId = UUID.randomUUID().toString(); } } else { // Example 128. NodeID already exists if (nodes.keySet().contains(nodeId)) throw new XMPPError(StanzaError.conflict, "Node: '" + nodeId + "' already exists"); } // Create Node PubsubNode newNode = new PubsubNode(owner, nodeId); nodes.put(nodeId, newNode); endpoint.addRootNode(newNode); writeToDAO(newNode); // Build success response Pubsub response = new Pubsub(); Create value = new Create(); value.setNode(nodeId); response.setCreate(value); return response; } @Override public Pubsub ownerCreateConfigure(Stanza stanza, Pubsub payload) throws XMPPError { // TODO 8.2 Unsupported u = new Unsupported(); u.setFeature("config-node"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public org.jabber.protocol.pubsub.owner.Pubsub ownerConfigureRequest(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { // TODO 8.2 Unsupported u = new Unsupported(); u.setFeature("config-node"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public org.jabber.protocol.pubsub.owner.Pubsub ownerConfigureSubmission(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { // TODO 8.2 Unsupported u = new Unsupported(); u.setFeature("config-node"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } @Override public org.jabber.protocol.pubsub.owner.Pubsub ownerDefaultConfiguration(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { // TODO 8.2 Unsupported u = new Unsupported(); u.setFeature("config-node"); throw new XMPPError(StanzaError.feature_not_implemented, null, u); } // 8.4 Delete a Node @Override public void ownerDelete(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { String nodeId = payload.getDelete().getNode(); PubsubNode node = nodes.get(nodeId); // 8.4.3.2 Node Does Not Exist if (node==null) throw new XMPPError(StanzaError.item_not_found); // 8.4.3.1 Insufficient Privileges IIdentity sender = stanza.getFrom(); if (!node.getOwner().equals(sender)) throw new XMPPError(StanzaError.forbidden); // Remove Node nodes.remove(nodeId); endpoint.removeRootNode(node); writeToDAO(node); // Example 156. Owner deletes a node with redirection String redirectUri = null; if (payload.getDelete().getRedirect()!=null) { redirectUri = payload.getDelete().getRedirect().getUri(); redirectedNodes.put(nodeId, redirectUri); } // Example 158. Subscribers are notified of node deletion org.jabber.protocol.pubsub.event.Delete deleteEvent = new org.jabber.protocol.pubsub.event.Delete(); deleteEvent.setNode(nodeId); if (redirectUri!=null) { org.jabber.protocol.pubsub.event.Redirect redirectEvent = new org.jabber.protocol.pubsub.event.Redirect(); redirectEvent.setUri(redirectUri); deleteEvent.setRedirect(redirectEvent); } pes.sendEvent(node.getSubscribers(), deleteEvent); } // 8.5 Purge All Node Items @Override public void ownerPurgeItems(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { String nodeId = payload.getPurge().getNode(); PubsubNode node = nodes.get(nodeId); // 8.5.3.4 Node Does Not Exist if (node==null) throw new XMPPError(StanzaError.item_not_found); // 8.5.3.2 Insufficient Privileges IIdentity sender = stanza.getFrom(); if (!node.getOwner().equals(sender)) throw new XMPPError(StanzaError.forbidden); // Purge Items node.purge(); // Example 163. Subscribers are notified of node purge org.jabber.protocol.pubsub.event.Purge purgeEvent = new org.jabber.protocol.pubsub.event.Purge(); purgeEvent.setNode(nodeId); pes.sendEvent(node.getSubscribers(), purgeEvent); } // 8.8 Manage Subscriptions @Override public org.jabber.protocol.pubsub.owner.Pubsub ownerSubscriptions(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { String nodeId = payload.getSubscriptions().getNode(); PubsubNode node = nodes.get(nodeId); // Example 186. Node does not exist if (node==null) throw new XMPPError(StanzaError.item_not_found); // Example 185. Entity is not an owner IIdentity sender = stanza.getFrom(); if (!node.getOwner().equals(sender)) throw new XMPPError(StanzaError.forbidden); if (payload.getSubscriptions().getSubscription().size()>0) { // 8.8.2 Modify Subscriptions List<org.jabber.protocol.pubsub.owner.Subscription> subscriptions = new ArrayList<org.jabber.protocol.pubsub.owner.Subscription>(payload.getSubscriptions().getSubscription()); for (org.jabber.protocol.pubsub.owner.Subscription s : payload.getSubscriptions().getSubscription()) { List<String> subs; try { subs = node.getSubscriptions(idm.fromJid(s.getJid())); } catch (InvalidFormatException e) { throw new XMPPError(StanzaError.bad_request, null, ERROR_INVALID_JID); } if (s.getSubscription().equals(SUBSCRIPTION_SUBSCRIBED)) { if (subs==null) { node.newSubscription(sender); subscriptions.remove(s); } } if (s.getSubscription().equals(SUBSCRIPTION_NONE)) { if (subs!=null) { if (subs.size()==1) { node.unsubscribe(subs.get(0)); subscriptions.remove(s); } else { if (s.getSubid()!=null && subs.contains(s.getSubid())) { node.unsubscribe(s.getSubid()); subscriptions.remove(s); } } } } } // Empty success response if (subscriptions.size()==0) return null; // Error else return null; // TODO } else { // 8.8.1 Retrieve Subscriptions List for (IIdentity subscriber : node.getSubscribers()) { List<String> subscriberSubscriptions = node.getSubscriptions(subscriber); if (subscriberSubscriptions.size()==1) { org.jabber.protocol.pubsub.owner.Subscription sub = new org.jabber.protocol.pubsub.owner.Subscription(); sub.setJid(subscriber.getJid()); sub.setSubscription(SUBSCRIPTION_SUBSCRIBED); payload.getSubscriptions().getSubscription().add(sub); } else { for (String subId : subscriberSubscriptions) { org.jabber.protocol.pubsub.owner.Subscription sub = new org.jabber.protocol.pubsub.owner.Subscription(); sub.setJid(subscriber.getJid()); sub.setSubscription(SUBSCRIPTION_SUBSCRIBED); sub.setSubid(subId); payload.getSubscriptions().getSubscription().add(sub); } } } return payload; } } // 8.9 Manage Affiliations @Override public org.jabber.protocol.pubsub.owner.Pubsub ownerAffiliations(Stanza stanza, org.jabber.protocol.pubsub.owner.Pubsub payload) throws XMPPError { String nodeId = payload.getAffiliations().getNode(); PubsubNode node = nodes.get(nodeId); // Example 205. Node does not exist if (node==null) throw new XMPPError(StanzaError.item_not_found); // Example 204. Entity is not an owner IIdentity sender = stanza.getFrom(); if (!node.getOwner().equals(sender)) throw new XMPPError(StanzaError.forbidden); if (payload.getAffiliations().getAffiliation().size()>0) { // 8.9.2 Modify Affiliation // TODO } else { // 8.9.1 Retrieve Affiliations List // TODO } return null; } }