package org.societies.comm.xmpp.client.impl; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.util.PacketParserUtils; 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.societies.api.comm.xmpp.datatypes.Stanza; import org.societies.api.comm.xmpp.datatypes.StanzaError; import org.societies.api.comm.xmpp.exceptions.XMPPError; import org.societies.comm.android.ipc.utils.MarshallUtils; import org.societies.impl.RawXmlProvider; import org.societies.simple.converters.URIConverter; import org.societies.simple.converters.EventItemsConverter; import org.societies.simple.converters.PubsubItemConverter; import org.societies.simple.converters.PubsubItemsConverter; import org.societies.simple.converters.XMLGregorianCalendarConverter; import org.w3c.dom.DOMException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import android.util.Log; public class PacketMarshaller { private static final String LOG_TAG = PacketMarshaller.class.getName(); private final Map<String, String> nsToPackage = new HashMap<String, String>(); private Serializer s; public PacketMarshaller() { // Log.d(LOG_TAG, "Constructor"); Registry registry = new Registry(); Strategy strategy = new RegistryStrategy(registry); try { Element exampleElementImpl = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().createElement("dummy"); registry.bind(exampleElementImpl.getClass(), ElementConverter.class); Log.i(LOG_TAG, "Registered class '"+exampleElementImpl.getClass()+"' as ElementImpl"); // http://stackoverflow.com/questions/7918466/cant-use-xmlgregoriancalendar-in-android-even-if-it-is-documented // XMLGregorianCalendar gregorianCalendarImpl = DatatypeFactory.newInstance().newXMLGregorianCalendar(); // registry.bind(gregorianCalendarImpl.getClass(), XMLGregorianCalendarConverter.class); // Log.i(LOG_TAG, "Registered class '"+gregorianCalendarImpl.getClass()+"' as XMLGregorianCalendarImpl"); registry.bind(java.net.URI.class,URIConverter.class); registry.bind(org.jabber.protocol.pubsub.event.Items.class, new EventItemsConverter(s)); registry.bind(org.jabber.protocol.pubsub.Items.class, new PubsubItemsConverter(s)); registry.bind(org.jabber.protocol.pubsub.Item.class, new PubsubItemConverter(s)); } catch (DOMException e) { Log.e(LOG_TAG, "DOMException trying to get runtime ElementImpl"); } catch (ParserConfigurationException e) { Log.e(LOG_TAG, "ParserConfigurationException trying to get runtime ElementImpl"); } catch (Exception e) { Log.e(LOG_TAG, "Exception trying to register runtime ElementImpl or XMLGregorianCalendarImpl in SimpleXML",e); } s = new Persister(strategy); } public void register(List<String> elementNames, List<String> namespaces, List<String> packages) { // Log.d(LOG_TAG, "register"); for (String element : elementNames) { // Log.d(LOG_TAG, "register element: " + element); } try { //TODO: SIMPLE XML for (int i=0; i<packages.size(); i++) { String packageStr = packages.get(i); String nsStr = namespaces.get(i); nsToPackage.put(nsStr, packageStr); } RawXmlProvider rawXmlProvider = new RawXmlProvider(); for(String elementName:elementNames) for(String namespace:namespaces) ProviderManager.getInstance().addExtensionProvider(elementName, namespace, rawXmlProvider); } catch (Exception e) { throw new RuntimeException(e); } } public String marshallMessage(Stanza stanza, Message.Type type, Object payload) throws Exception { // Log.d(LOG_TAG, "marshallMessage type: " + type.name() + "stanza from : " + stanza.getFrom() + " to: " + stanza.getTo()); final String xml = marshallPayload(payload); Message message = new Message(); if(stanza.getId() != null) message.setPacketID(stanza.getId()); if(stanza.getFrom() != null) message.setFrom(stanza.getFrom().getJid()); if(type != null) message.setType(type); message.setTo(stanza.getTo().getJid()); message.addExtension(new PacketExtension() { public String getElementName() { return null; } public String getNamespace() { return null; } public String toXML() { return xml; } }); return message.toXML(); } public String marshallIQ(Stanza stanza, IQ.Type type, Object payload) throws Exception { // Log.d(LOG_TAG, "marshallIQ type: " + type + "stanza from : " + stanza.getFrom() + " to: " + stanza.getTo()); final String xml = marshallPayload(payload); IQ iq = new IQ() { @Override public String getChildElementXML() { return xml; } }; if(stanza.getId() != null) iq.setPacketID(stanza.getId()); if(stanza.getFrom() != null) iq.setFrom(stanza.getFrom().getJid()); iq.setTo(stanza.getTo().getJid()); iq.setType(type); return iq.toXML(); } public IQ unmarshallIq(String xml) throws Exception { // Log.d(LOG_TAG, "unmarshallIq xml: " + xml); return parseIq(createXmlPullParser(xml)); } public Message unmarshallMessage(String xml) throws Exception { // Log.d(LOG_TAG, "unmarshallMessage + xml: " + xml); return (Message)PacketParserUtils.parseMessage(createXmlPullParser(xml)); } public Object unmarshallPayload(Packet packet) throws Exception { // Log.d(LOG_TAG, "unmarshallPayload packet: " + packet.getPacketID()); Element element = getElementAny(packet); if(element == null) // Empty stanza return null; String namespace = element.lookupNamespaceURI(element.getPrefix()); String xml = MarshallUtils.nodeToString(element); // Log.d(PacketMarshaller.class.getName() + " ### ", xml); //GET CLASS FIRST String packageStr = nsToPackage.get(namespace); String beanName = element.getLocalName().substring(0,1).toUpperCase() + element.getLocalName().substring(1); //NEEDS TO BE "CalcBean", not "calcBean" // Log.d(PacketMarshaller.class.getName(), "Trying to unmarshall: " + packageStr + "." + beanName); Class<?> c = Class.forName(packageStr + "." + beanName); Object payload = s.read(c, xml); return payload; } public Entry<String, List<String>> parseItemsResult(Packet packet) throws SAXException, IOException, ParserConfigurationException { // Log.d(LOG_TAG, "Entry"); Element element = getElementAny(packet); final String node = element.getAttribute("node"); final List<String> list = new ArrayList<String>(); NodeList childs = element.getChildNodes(); for(int i=0; i<childs.getLength(); i++) { Node child = childs.item(i); if(child instanceof Element) { Element childElement = (Element)child; list.add(childElement.getAttribute("node")); } } return new Entry<String, List<String>>() { public String getKey() { return node; } public List<String> getValue() { return list; } public List<String> setValue(List<String> value) { throw new UnsupportedOperationException("Immutable object."); } }; } public XMPPError unmarshallError(Packet packet) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Element errorElement = (Element) factory.newDocumentBuilder().parse(new InputSource(new StringReader(packet.toXML()))).getDocumentElement().getFirstChild(); String errorElementName = firstElement(errorElement.getChildNodes()).getTagName(); // TODO assumes the stanza error comes first StanzaError stanzaError = StanzaError.valueOf(errorElementName.replaceAll("-", "_")); return new XMPPError(stanzaError, null); // TODO parse application error } private String marshallPayload(Object payload) { // Log.d(LOG_TAG, "marshallPayload payload: " + payload.getClass().getName()); ByteArrayOutputStream os = new ByteArrayOutputStream(); try { s.write(payload, os); } catch (Exception e) { e.printStackTrace(); } // Log.d(PacketMarshaller.class.getName() + " ### ", os.toString()); return os.toString(); } /** Get the element with the payload out of the XMPP packet. * @throws ParserConfigurationException */ private Element getElementAny(Packet packet) throws SAXException, IOException, ParserConfigurationException { // Log.d(LOG_TAG, "getElementAny packet: " + packet.getPacketID()); if (packet instanceof IQ) { // According to the schema in RCF6121 IQs only have one // element, unless they have an error DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); return (Element) factory.newDocumentBuilder().parse(new InputSource(new StringReader(packet.toXML()))).getDocumentElement().getFirstChild(); } else if (packet instanceof Message) { // according to the schema in RCF6121 messages have an unbounded // number // of "subject", "body" or "thread" elements before the any element // part DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); Element element = factory.newDocumentBuilder().parse(new InputSource(new StringReader(packet.toXML()))).getDocumentElement(); NodeList childs = element.getChildNodes(); for(int i=0; i<childs.getLength(); i++) { Node child = childs.item(i); if(child instanceof Element) { Element childElem = (Element)child; String namespace = childElem.lookupNamespaceURI(childElem.getPrefix()); if(!namespace.equals("jabber:client") && !namespace.equals("jabber:server")) return childElem; } } throw new RuntimeException("Got a Message with no payload element."); } else { throw new RuntimeException("Got Packet type that I could not handle: " + packet.getClass().getName()); } } private XmlPullParser createXmlPullParser(String xml) throws XmlPullParserException, IOException { // Log.d(LOG_TAG, "createXmlPullParser xml: " + xml); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser parser = factory.newPullParser(); parser.setInput(new StringReader(xml)); parser.next(); return parser; } private IQ parseIq(XmlPullParser parser) throws Exception { // Log.d(LOG_TAG, "parseIq"); IQ iqPacket = null; String id = parser.getAttributeValue("", "id"); String to = parser.getAttributeValue("", "to"); String from = parser.getAttributeValue("", "from"); IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { iqPacket = new RawXmlProvider().parseIQ(parser); } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals("iq")) { done = true; } } } if(iqPacket == null) { iqPacket = new IQ() { public String getChildElementXML() { return ""; } }; } // Set basic values on the iq packet. iqPacket.setPacketID(id); iqPacket.setTo(to); iqPacket.setFrom(from); iqPacket.setType(type); return iqPacket; } private Element firstElement(NodeList nodes) { for(int i=0; i<nodes.getLength(); i++) if(nodes.item(i) instanceof Element) return (Element)nodes.item(i); throw new IllegalArgumentException("There is no Element in the given node list."); } }