package org.societies.android.platform.androidutils; import android.util.Log; 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.android.api.comms.xmpp.Stanza; import org.societies.android.api.comms.xmpp.StanzaError; import org.societies.android.api.comms.xmpp.XMPPError; import org.societies.simple.basic.URIConverter; import org.societies.simple.converters.EventItemsConverter; import org.societies.simple.converters.PubsubItemConverter; import org.societies.simple.converters.PubsubItemsConverter; 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 javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; 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; public class PacketMarshaller { private static final String LOG_TAG = PacketMarshaller.class.getCanonicalName(); 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() { @Override public String getElementName() { return null; } @Override public String getNamespace() { return null; } @Override 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.getCanonicalName() + " ### ", 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.getCanonicalName(), "Trying to unmarshall: " + packageStr + "." + beanName); Class<?> c = Class.forName(packageStr + "." + beanName); Object payload; try { payload = s.read(c, xml); } catch (Exception ex) { Log.e(LOG_TAG, "Error deserializing object", ex); throw ex; } 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>>() { @Override public String getKey() { return node; } @Override public List<String> getValue() { return list; } @Override 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.getCanonicalName() + " ### ", 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() { @Override 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."); } }