package org.societies.comms;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.jabber.protocol.pubsub.Create;
import org.jabber.protocol.pubsub.Item;
import org.jabber.protocol.pubsub.Items;
import org.jabber.protocol.pubsub.Publish;
import org.jabber.protocol.pubsub.Pubsub;
import org.jabber.protocol.pubsub.Retract;
import org.jabber.protocol.pubsub.Subscribe;
import org.jabber.protocol.pubsub.Unsubscribe;
import org.jabber.protocol.pubsub.owner.Affiliations;
import org.jabber.protocol.pubsub.owner.Delete;
import org.jabber.protocol.pubsub.owner.Purge;
import org.jabber.protocol.pubsub.owner.Subscriptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.societies.api.comm.xmpp.datatypes.Stanza;
import org.societies.api.comm.xmpp.datatypes.StanzaError;
import org.societies.api.comm.xmpp.datatypes.XMPPInfo;
import org.societies.api.comm.xmpp.exceptions.CommunicationException;
import org.societies.api.comm.xmpp.exceptions.XMPPError;
import org.societies.api.comm.xmpp.interfaces.ICommCallback;
import org.societies.api.comm.xmpp.interfaces.ICommManager;
import org.societies.api.comm.xmpp.pubsub.Affiliation;
import org.societies.api.comm.xmpp.pubsub.Subscription;
import org.societies.api.comm.xmpp.pubsub.SubscriptionState;
import org.societies.api.identity.IIdentity;
import org.societies.api.identity.InvalidFormatException;
import org.societies.comm.android.ipc.utils.MarshallUtils;
import org.societies.pubsub.interfaces.ISubscriber;
import org.societies.pubsub.interfaces.SubscriptionParcelable;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
public class PubsubClientImpl implements org.societies.pubsub.interfaces.Pubsub, ICommCallback {
public static final int TIMEOUT = 10000;
private final static List<String> NAMESPACES = Collections
.unmodifiableList(Arrays.asList("http://jabber.org/protocol/pubsub",
"http://jabber.org/protocol/pubsub#errors",
"http://jabber.org/protocol/pubsub#event",
"http://jabber.org/protocol/pubsub#owner",
"http://jabber.org/protocol/disco#items"));
private static final List<String> PACKAGES = Collections
.unmodifiableList(Arrays.asList("org.jabber.protocol.pubsub",
"org.jabber.protocol.pubsub.errors",
"org.jabber.protocol.pubsub.event",
"org.jabber.protocol.pubsub.owner"));
private static final List<String> ELEMENTS = Collections.unmodifiableList(
Arrays.asList("pubsub",
"event",
"query"));
private static Logger LOG = LoggerFactory
.getLogger(PubsubClientImpl.class);
private ICommManager endpoint;
private Map<String,Object> responses;
private Map<Subscription,List<ISubscriber>> subscribers;
public PubsubClientImpl(ICommManager endpoint) {
responses = new HashMap<String, Object>();
subscribers = new HashMap<Subscription, List<ISubscriber>>();
this.endpoint = endpoint;
try {
endpoint.register(this);
} catch (CommunicationException e) {
LOG.error(e.getMessage());
}
}
public ICommManager getICommManager() {
return endpoint;
}
public List<String> getXMLNamespaces() {
return NAMESPACES;
}
public static List<String> getXMLElements() {
return ELEMENTS;
}
public List<String> getJavaPackages() {
return PACKAGES;
}
public void receiveMessage(Stanza stanza, Object payload) {
if (payload instanceof org.jabber.protocol.pubsub.event.Event) {
org.jabber.protocol.pubsub.event.Items items = ((org.jabber.protocol.pubsub.event.Event)payload).getItems();
String node = items.getNode();
Subscription sub = new Subscription(stanza.getFrom(), stanza.getTo(), node, null); // TODO may break due to mismatch between "to" and local IIdentity
org.jabber.protocol.pubsub.event.Item i = items.getItem().get(0); // TODO assume only one item per notification
try {
List<ISubscriber> subscriberList = subscribers.get(sub);
for (ISubscriber subscriber : subscriberList)
subscriber.pubsubEvent(stanza.getFrom().getJid(), node, i.getId(), MarshallUtils.nodeToString((Element)i.getAny()));
} catch (TransformerException e) {
LOG.error("Error while unmarshalling pubsub event payload", e);
}
}
}
// TODO subId
// <message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo'>
// <event xmlns='http://jabber.org/protocol/pubsub#event'>
// <items node='princely_musings'>
// <item id='ae890ac52d0df67ed7cfdf51b644e901'/>
// </items>
// </event>
// <headers xmlns='http://jabber.org/protocol/shim'>
// <header name='SubID'>123-abc</header>
// <header name='SubID'>004-yyy</header>
// </headers>
// </message>
public void receiveResult(Stanza stanza, Object payload) {
synchronized (responses) {
LOG.info("receiveResult 4 id "+stanza.getId());
responses.put(stanza.getId(), payload);
responses.notifyAll();
}
}
public void receiveError(Stanza stanza, XMPPError error) {
synchronized (responses) {
LOG.info("receiveError 4 id "+stanza.getId());
responses.put(stanza.getId(), error);
responses.notifyAll();
}
}
public void receiveInfo(Stanza stanza, String node, XMPPInfo info) {
// TODO Auto-generated method stub
}
public void receiveItems(Stanza stanza, final String node, final List<String> items) {
Entry<String, List<String>> mapSimpleEntry = new Entry<String, List<String>>() {
public String getKey() {
return node;
}
public List<String> getValue() {
return items;
}
public List<String> setValue(List<String> value) {
throw new UnsupportedOperationException("Immutable object.");
}
};
synchronized (responses) {
LOG.info("receiveItems 4 id "+stanza.getId());
responses.put(stanza.getId(), mapSimpleEntry);
responses.notifyAll();
}
}
private Object blockingIQ(Stanza stanza, Object payload) throws CommunicationException, XMPPError {
endpoint.sendIQSet(stanza, payload, this);
return waitForResponse(stanza.getId());
}
private Object waitForResponse(String id) throws XMPPError {
Object response = null;
synchronized (responses) {
while (!responses.containsKey(id)) {
try {
LOG.info("waiting response 4 id "+id);
responses.wait(TIMEOUT);
} catch (InterruptedException e) {
LOG.info(e.getMessage());
}
LOG.info("checking response 4 id "+id+" in "+Arrays.toString(responses.keySet().toArray()));
}
response = responses.remove(id);
LOG.info("got response 4 id "+id);
}
if (response instanceof XMPPError)
throw (XMPPError)response;
return response;
}
public List<String> discoItems(String pubsubService, String node)
throws XMPPError, CommunicationException {
String id = endpoint.getItems(convertStringToIdentity(pubsubService), node, this);
Object response = waitForResponse(id);
// TODO node check
// String returnedNode = ((SimpleEntry<String, List<XMPPNode>>)response).getKey();
// if (returnedNode != node)
// throw new CommunicationException("");
if (response!=null)
return ((Entry<String, List<String>>)response).getValue();
else
return null;
}
public SubscriptionParcelable subscriberSubscribe(String pubsubService, String node,
ISubscriber subscriber) throws XMPPError, CommunicationException {
IIdentity pubsubServiceIdentity = convertStringToIdentity(pubsubService);
Subscription subscription = new Subscription(pubsubServiceIdentity, localIdentity(), node, null);
List<ISubscriber> subscriberList = subscribers.get(subscription);
if (subscriberList==null) {
subscriberList = new ArrayList<ISubscriber>();
Stanza stanza = new Stanza(pubsubServiceIdentity);
Pubsub payload = new Pubsub();
Subscribe sub = new Subscribe();
sub.setJid(localIdentity().getBareJid());
sub.setNode(node);
payload.setSubscribe(sub);
Object response = blockingIQ(stanza, payload);
String subId = ((Pubsub)response).getSubscription().getSubid();
subscription = new Subscription(pubsubServiceIdentity, localIdentity(), node, subId);
subscribers.put(subscription, subscriberList);
}
subscriberList.add(subscriber);
return new SubscriptionParcelable(subscription, endpoint.getIdManager());
}
public void subscriberUnsubscribe(String pubsubService, String node,
ISubscriber subscriber) throws XMPPError,
CommunicationException {
IIdentity pubsubServiceIdentity = convertStringToIdentity(pubsubService);
Subscription subscription = new Subscription(pubsubServiceIdentity, localIdentity(), node, null);
List<ISubscriber> subscriberList = subscribers.get(subscription);
// subscriberList.remove(subscriber); // TODO Unsubscribes all subcribers. Change
subscriberList.clear(); // to allow single unsubscription.
if (subscriberList.size()==0) {
Stanza stanza = new Stanza(pubsubServiceIdentity);
Pubsub payload = new Pubsub();
Unsubscribe unsub = new Unsubscribe();
unsub.setJid(localIdentity().getJid());
unsub.setNode(node);
payload.setUnsubscribe(unsub);
Object response = blockingIQ(stanza, payload);
}
}
public List<Element> subscriberRetrieveLast(IIdentity pubsubService,
String node, String subId) throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Items items = new Items();
items.setNode(node);
if (subId!=null)
items.setSubid(subId);
// TODO max items... in the server also!
payload.setItems(items);
Object response = blockingIQ(stanza, payload);
List<Item> itemList = ((Pubsub)response).getItems().getItem();
List<Element> returnList = new ArrayList<Element>();
for (Item i : itemList)
returnList.add((Element) i.getAny());
return returnList;
}
public List<Element> subscriberRetrieveSpecific(IIdentity pubsubService,
String node, String subId, List<String> itemIdList)
throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Items items = new Items();
items.setNode(node);
if (subId!=null)
items.setSubid(subId);
for(String itemId : itemIdList) {
Item item = new Item();
item.setId(itemId);
items.getItem().add(item);
}
payload.setItems(items);
Object response = blockingIQ(stanza, payload);
List<Item> itemList = ((Pubsub)response).getItems().getItem();
List<Element> returnList = new ArrayList<Element>();
for (Item i : itemList)
returnList.add((Element) i.getAny());
return returnList;
}
public String publisherPublish(String pubsubServiceJid, String node,
String itemId, String item) throws XMPPError,
CommunicationException {
Stanza stanza = new Stanza(convertStringToIdentity(pubsubServiceJid));
Pubsub payload = new Pubsub();
Publish p = new Publish();
p.setNode(node);
Item i = new Item();
if (itemId!=null)
i.setId(itemId);
try {
i.setAny(MarshallUtils.stringToElement(item));
} catch (SAXException e) {
LOG.error("SAXException when parsing string to XML Element", e);
} catch (IOException e) {
LOG.error("IOException when parsing string to XML Element", e);
} catch (ParserConfigurationException e) {
LOG.error("ParserConfigurationException when parsing string to XML Element", e);
}
//i.setAny(item); // TODO
p.setItem(i);
payload.setPublish(p);
Object response = blockingIQ(stanza, payload);
try {
return ((Pubsub)response).getPublish().getItem().getId();
} catch(NullPointerException e) { // 7.1.2 the IQ-result SHOULD include an empty <item/> element that specifies the ItemID of the published item.
return null;
}
}
public void publisherDelete(String pubsubServiceJid, String node,
String itemId) throws XMPPError, CommunicationException {
IIdentity pubsubService = convertStringToIdentity(pubsubServiceJid);
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Retract retract = new Retract();
retract.setNode(node);
Item i = new Item();
i.setId(itemId);
retract.getItem().add(i);
payload.setRetract(retract);
Object response = blockingIQ(stanza, payload);
}
public void ownerCreate(String pubsubService, String node)
throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(convertStringToIdentity(pubsubService));
Pubsub payload = new Pubsub();
Create c = new Create();
c.setNode(node);
payload.setCreate(c);
blockingIQ(stanza, payload);
}
public void ownerDelete(String pubsubService, String node)
throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(convertStringToIdentity(pubsubService));
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Delete delete = new Delete();
delete.setNode(node);
payload.setDelete(delete);
blockingIQ(stanza, payload);
}
public void ownerPurgeItems(String pubsubServiceJid, String node)
throws XMPPError, CommunicationException {
IIdentity pubsubService = convertStringToIdentity(pubsubServiceJid);
Stanza stanza = new Stanza(pubsubService);
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Purge purge = new Purge();
purge.setNode(node);
payload.setPurge(purge);
blockingIQ(stanza, payload);
}
public Map<IIdentity, SubscriptionState> ownerGetSubscriptions(
IIdentity pubsubService, String node) throws XMPPError,
CommunicationException {
Stanza stanza = new Stanza(pubsubService);
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Subscriptions subs = new Subscriptions();
subs.setNode(node);
payload.setSubscriptions(subs);
blockingIQ(stanza, payload);
List<org.jabber.protocol.pubsub.owner.Subscription> subList = ((org.jabber.protocol.pubsub.owner.Pubsub)payload).getSubscriptions().getSubscription();
Map<IIdentity, SubscriptionState> returnMap = new HashMap<IIdentity, SubscriptionState>();
for (org.jabber.protocol.pubsub.owner.Subscription s : subList)
try {
returnMap.put(endpoint.getIdManager().fromJid(s.getJid()), SubscriptionState.valueOf(s.getSubscription()));
} catch (InvalidFormatException e) {
LOG.warn("Unable to parse subscription JIDs",e);
}
return returnMap;
}
public Map<IIdentity, Affiliation> ownerGetAffiliations(
IIdentity pubsubService, String node) throws XMPPError,
CommunicationException {
Stanza stanza = new Stanza(pubsubService);
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Affiliations affs = new Affiliations();
affs.setNode(node);
payload.setAffiliations(affs);
blockingIQ(stanza, payload);
List<org.jabber.protocol.pubsub.owner.Affiliation> affList = ((org.jabber.protocol.pubsub.owner.Pubsub)payload).getAffiliations().getAffiliation();
Map<IIdentity, Affiliation> returnMap = new HashMap<IIdentity, Affiliation>();
for (org.jabber.protocol.pubsub.owner.Affiliation a : affList)
try {
returnMap.put(endpoint.getIdManager().fromJid(a.getJid()), Affiliation.valueOf(a.getAffiliation()));
} catch (InvalidFormatException e) {
LOG.warn("Unable to parse affiliation JIDs",e);
}
return returnMap;
}
public void ownerSetSubscriptions(IIdentity pubsubService, String node,
Map<IIdentity, SubscriptionState> subscriptions) throws XMPPError,
CommunicationException {
Stanza stanza = new Stanza(pubsubService);
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Subscriptions subs = new Subscriptions();
subs.setNode(node);
payload.setSubscriptions(subs);
for (IIdentity subscriber : subscriptions.keySet()) {
org.jabber.protocol.pubsub.owner.Subscription s = new org.jabber.protocol.pubsub.owner.Subscription();
s.setJid(subscriber.getJid());
s.setSubscription(subscriptions.get(subscriber).toString());
subs.getSubscription().add(s);
}
blockingIQ(stanza, payload);
// TODO error handling on multiple subscription changes
}
public void ownerSetAffiliations(IIdentity pubsubService, String node,
Map<IIdentity, Affiliation> affiliations) throws XMPPError,
CommunicationException {
Stanza stanza = new Stanza(pubsubService);
org.jabber.protocol.pubsub.owner.Pubsub payload = new org.jabber.protocol.pubsub.owner.Pubsub();
Affiliations affs = new Affiliations();
affs.setNode(node);
payload.setAffiliations(affs);
for (IIdentity subscriber : affiliations.keySet()) {
org.jabber.protocol.pubsub.owner.Affiliation a = new org.jabber.protocol.pubsub.owner.Affiliation();
a.setJid(subscriber.getJid());
a.setAffiliation(affiliations.get(subscriber).toString());
affs.getAffiliation().add(a);
}
blockingIQ(stanza, payload);
// TODO error handling on multiple affiliation changes
}
private IIdentity convertStringToIdentity(String jid) throws XMPPError {
try {
return endpoint.getIdManager().fromJid(jid);
} catch (InvalidFormatException e) {
throw new XMPPError(StanzaError.jid_malformed, "Invalid JID: "+jid);
}
}
private IIdentity localIdentity() {
return endpoint.getIdManager().getThisNetworkNode();
}
}