package org.societies.comm.xmpp.pubsub.impl;
import com.sun.org.apache.xerces.internal.dom.ElementNSImpl;
import org.jabber.protocol.pubsub.*;
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.simpleframework.xml.Namespace;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.convert.Registry;
import org.simpleframework.xml.convert.RegistryStrategy;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.strategy.Strategy;
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.*;
import org.societies.api.comm.xmpp.pubsub.Subscription;
import org.societies.api.identity.IIdentity;
import org.societies.api.identity.IIdentityManager;
import org.societies.api.identity.InvalidFormatException;
import org.societies.simple.basic.URIConverter;
import org.societies.simple.converters.XMLGregorianCalendarConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
@Component
public class PubsubClientImpl implements PubsubClient, ICommCallback {
public static final int WAIT_TIMEOUT = 10000;
public static final long REQUEST_TIMEOUT = 20000;
private final static List<String> NAMESPACES = Collections
.unmodifiableList(Arrays.asList("http://jabber.org/protocol/pubsub#event",
"http://jabber.org/protocol/pubsub",
"http://jabber.org/protocol/pubsub#errors",
"http://jabber.org/protocol/pubsub#owner",
"jabber:x:data"));
private static final List<String> PACKAGES = Collections
.unmodifiableList(Arrays.asList("org.jabber.protocol.pubsub.event",
"org.jabber.protocol.pubsub",
"org.jabber.protocol.pubsub.errors",
"org.jabber.protocol.pubsub.owner",
"jabber.x.data"));
private static Logger LOG = LoggerFactory
.getLogger(PubsubClientImpl.class);
private ICommManager endpoint;
private final Map<String, Object> responses;
private final Map<String, Long> responseTimeout;
private final Map<Subscription, List<Subscriber>> subscribers;
private IIdentityManager idm;
private final Map<String, String> nsToPackage = new HashMap<String, String>();
private String packagesContextPath;
private IIdentity localIdentity;
private final Map<String, Class<?>> elementToClass;
private Serializer serializer;
@Autowired
public PubsubClientImpl(ICommManager endpoint) {
responses = new HashMap<String, Object>();
responseTimeout = new HashMap<String, Long>();
subscribers = new HashMap<Subscription, List<Subscriber>>();
elementToClass = new HashMap<String, Class<?>>();
Registry registry = new Registry();
try {
registry.bind(com.sun.org.apache.xerces.internal.jaxp.datatype.XMLGregorianCalendarImpl.class, XMLGregorianCalendarConverter.class);
registry.bind(java.net.URI.class, URIConverter.class);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
Strategy strategy = new RegistryStrategy(registry);
serializer = new Persister(strategy);
this.endpoint = endpoint;
idm = endpoint.getIdManager();
try {
if (endpoint.isConnected())
localIdentity = idm.getThisNetworkNode();
else
throw new CommunicationException("Injected endpoint is not connected!");
packagesContextPath = "";
endpoint.register(this);
} catch (CommunicationException e) {
LOG.error(e.getMessage(), e);
}
}
public ICommManager getICommManager() {
return endpoint;
}
/*
* CommCallback Impl
*/
@Override
public List<String> getXMLNamespaces() {
return NAMESPACES;
}
@Override
public List<String> getJavaPackages() {
return PACKAGES;
}
/**
* Retrieves a package from a namespace mapping
*/
private String getPackage(String namespace) {
return nsToPackage.get(namespace);
}
@Override
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
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
Object bean = unmarshallBean((ElementNSImpl) i.getAny(),true);
//POST EVENT
List<Subscriber> subscriberList = subscribers.get(sub);
for (Subscriber subscriber : subscriberList)
subscriber.pubsubEvent(stanza.getFrom(), node, i.getId(), bean);
Thread.currentThread().setContextClassLoader(oldCl);
}
}
// 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>
private Object unmarshallBean(ElementNSImpl eventBean, boolean clSwitch) {
Object bean = null;
//CONVERT THE .getAny() OBJECT TO XML
DOMImplementationLS domImplLS = (DOMImplementationLS) eventBean.getOwnerDocument().getImplementation();
LSSerializer domSerializer = domImplLS.createLSSerializer();
String eventBeanXML = domSerializer.writeToString(eventBean);
//SERIALISE OBJECT
String elementID = "{" + eventBean.getNamespaceURI() + "}" + eventBean.getLocalName();
Class<?> c = elementToClass.get(elementID);
if (c == null) {
// when received xml was has not been binded print a warn and notify with raw XML
String[] keyArray = new String[elementToClass.keySet().size()];
keyArray = elementToClass.keySet().toArray(keyArray);
LOG.warn("No class found for namespace{element} '" + elementID + "', passing unmarshalled XML element... Registered entries are: " + Arrays.toString(keyArray));
bean = eventBean;
} else {
// unmarshall item content
if (clSwitch)
Thread.currentThread().setContextClassLoader(c.getClassLoader());
try {
bean = serializer.read(c, eventBeanXML);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
return bean;
}
@Override
public void receiveResult(Stanza stanza, Object payload) {
synchronized (responses) {
// LOG.info("receiveResult 4 id "+stanza.getId());
responses.put(stanza.getId(), payload);
responses.notifyAll();
}
}
@Override
public void receiveError(Stanza stanza, XMPPError error) {
synchronized (responses) {
// LOG.info("receiveError 4 id "+stanza.getId());
responses.put(stanza.getId(), error);
responses.notifyAll();
}
}
@Override
public void receiveInfo(Stanza stanza, String node, XMPPInfo info) {
// TODO Auto-generated method stub
}
@Override
public void receiveItems(Stanza stanza, String node, List<String> items) {
SimpleEntry<String, List<String>> mapSimpleEntry = new AbstractMap.SimpleEntry<String, List<String>>(node, items);
synchronized (responses) {
// LOG.info("receiveItems 4 id "+stanza.getId());
responses.put(stanza.getId(), mapSimpleEntry);
responses.notifyAll();
}
}
/*
* PubsubClient Impl - emulates synchronous
*/
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 {
synchronized (responseTimeout) {
responseTimeout.put(id, REQUEST_TIMEOUT);
Object response = null;
synchronized (responses) {
while (!responses.containsKey(id)) {
long cycleStart = System.currentTimeMillis();
try {
// LOG.info("waiting response 4 id "+id);
responses.wait(WAIT_TIMEOUT);
} catch (InterruptedException e) {
LOG.info(e.getMessage(), e);
}
// LOG.info("checking response 4 id "+id+" in "+Arrays.toString(responses.keySet().toArray()));
long timeLeft = responseTimeout.get(id) - System.currentTimeMillis() + cycleStart;
if (timeLeft > 0)
responseTimeout.put(id, timeLeft);
else {
LOG.warn("Result for request with id='" + id + "' timed out...");
throw new XMPPError(StanzaError.remote_server_timeout);
}
}
response = responses.remove(id);
// LOG.info("got response 4 id "+id);
}
if (response instanceof XMPPError)
throw (XMPPError) response;
return response;
}
}
@Override
public List<String> discoItems(IIdentity pubsubService, String node)
throws XMPPError, CommunicationException {
String id = endpoint.getItems(pubsubService, node, this);
Object response = waitForResponse(id);
// TODO node check
// String returnedNode = ((SimpleEntry<String, List<XMPPNode>>)response).getKey();
// if (returnedNode != node)
// throw new CommunicationException("");
return ((SimpleEntry<String, List<String>>) response).getValue();
}
@Override
public Subscription subscriberSubscribe(IIdentity pubsubService, String node,
Subscriber subscriber) throws XMPPError, CommunicationException {
Subscription subscription = new Subscription(pubsubService, localIdentity, node, null);
List<Subscriber> subscriberList = subscribers.get(subscription);
if (subscriberList == null) {
subscriberList = new ArrayList<Subscriber>();
Stanza stanza = new Stanza(pubsubService);
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(pubsubService, localIdentity, node, subId);
subscribers.put(subscription, subscriberList);
}
subscriberList.add(subscriber);
return subscription;
}
@Override
public void subscriberUnsubscribe(IIdentity pubsubService, String node,
Subscriber subscriber) throws XMPPError,
CommunicationException {
Subscription subscription = new Subscription(pubsubService, localIdentity, node, null);
List<Subscriber> subscriberList = subscribers.get(subscription);
subscriberList.remove(subscriber);
if (subscriberList.size() == 0) {
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Unsubscribe unsub = new Unsubscribe();
unsub.setJid(localIdentity.getBareJid());
unsub.setNode(node);
payload.setUnsubscribe(unsub);
Object response = blockingIQ(stanza, payload);
}
}
@Override
public List<Object> 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<Object> returnList = new ArrayList<Object>();
for (Item i : itemList)
returnList.add(unmarshallBean((ElementNSImpl) i.getAny(), false));
return returnList;
}
@Override
public List<Object> 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<Object> returnList = new ArrayList<Object>();
for (Item i : itemList)
returnList.add(unmarshallBean((ElementNSImpl) i.getAny(), false));
return returnList;
}
@Override
public String publisherPublish(IIdentity pubsubService, String node,
String itemId, Object item) throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Publish p = new Publish();
p.setNode(node);
Item i = new Item();
if (itemId != null)
i.setId(itemId);
i.setAny(item);
p.setItem(i);
payload.setPublish(p);
Object response = blockingIQ(stanza, payload);
if (response != null)
return ((Pubsub) response).getPublish().getItem().getId();
else
return itemId;
}
@Override
public void publisherDelete(IIdentity pubsubService, String node,
String itemId) throws XMPPError, CommunicationException {
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);
}
@Override
public void ownerCreate(IIdentity pubsubService, String node)
throws XMPPError, CommunicationException {
Stanza stanza = new Stanza(pubsubService);
Pubsub payload = new Pubsub();
Create c = new Create();
c.setNode(node);
payload.setCreate(c);
blockingIQ(stanza, payload);
}
@Override
public void ownerDelete(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();
Delete delete = new Delete();
delete.setNode(node);
payload.setDelete(delete);
blockingIQ(stanza, payload);
}
@Override
public void ownerPurgeItems(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();
Purge purge = new Purge();
purge.setNode(node);
payload.setPurge(purge);
blockingIQ(stanza, payload);
}
@Override
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 = payload.getSubscriptions().getSubscription();
Map<IIdentity, SubscriptionState> returnMap = new HashMap<IIdentity, SubscriptionState>();
for (org.jabber.protocol.pubsub.owner.Subscription s : subList)
try {
returnMap.put(idm.fromJid(s.getJid()), SubscriptionState.valueOf(s.getSubscription()));
} catch (InvalidFormatException e) {
LOG.warn("Unable to parse subscription JIDs", e);
}
return returnMap;
}
@Override
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 = payload.getAffiliations().getAffiliation();
Map<IIdentity, Affiliation> returnMap = new HashMap<IIdentity, Affiliation>();
for (org.jabber.protocol.pubsub.owner.Affiliation a : affList)
try {
returnMap.put(idm.fromJid(a.getJid()), Affiliation.valueOf(a.getAffiliation()));
} catch (InvalidFormatException e) {
LOG.warn("Unable to parse affiliation JIDs", e);
}
return returnMap;
}
@Override
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
}
@Override
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
}
@Override
public synchronized void addJaxbPackages(List<String> packageList) { //throws JAXBException {
if (packagesContextPath.length() == 0) {
// TODO first run!
}
StringBuilder contextPath = new StringBuilder(packagesContextPath);
for (String pack : packageList)
contextPath.append(":").append(pack);
/*
JAXBContext jc = JAXBContext.newInstance(contextPath.toString(),
this.getClass().getClassLoader());
contentUnmarshaller = jc.createUnmarshaller();
contentMarshaller = jc.createMarshaller();
*/
//TODO: SIMPLE
try {
for (int i = 0; i < packageList.size(); i++) {
String packageStr = packageList.get(i);
String nsStr = getNSfromPackage(packageStr);
nsToPackage.put(nsStr, packageStr);
}
} catch (Exception ex) {
LOG.error("Error in JAXBMapping adding: " + ex.getMessage());
}
packagesContextPath = contextPath.toString();
}
/**
* Returns the Namespace for a Package string
*
*/
private static String getNSfromPackage(String packageString) {
String ns;
String[] packArr = packageString.split("\\.");
ns = "http://" + packArr[1] + "." + packArr[0];
for (int i = 2; i < packArr.length; i++)
ns += "/" + packArr[i];
return ns;
}
@Override
public void addSimpleClasses(List<String> classList) throws ClassNotFoundException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
for (String c : classList) {
Class<?> clazz = cl.loadClass(c);
Root rootAnnotation = clazz.getAnnotation(Root.class);
Namespace namespaceAnnotation = clazz.getAnnotation(Namespace.class);
if (rootAnnotation != null && namespaceAnnotation != null) {
elementToClass.put("{" + namespaceAnnotation.reference() + "}" + rootAnnotation.name(), clazz);
}
}
} catch (ClassNotFoundException e) {
throw new ClassNotFoundException(e.getMessage() + " from classloader " + cl.toString() + ": class being registered from a context that doesn't have access to it.", e);
}
}
}