package org.societies.impl; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.jivesoftware.smack.AccountManager; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smackx.packet.DiscoverItems; import org.societies.api.comm.xmpp.datatypes.XMPPNode; import org.societies.api.comm.xmpp.exceptions.CommunicationException; import org.societies.interfaces.Callback; import org.societies.interfaces.XMPPAgent; 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 android.util.Log; public class XMPPClient implements XMPPAgent { private static final String LOG_TAG = XMPPClient.class.getName(); private XMPPConnection connection; private String username, password, resource; private int usingConnectionCounter = 0; private ProviderElementNamespaceRegistrar providerRegistrar = new ProviderElementNamespaceRegistrar(); private RawXmlProvider rawXmlProvider = new RawXmlProvider(); private Configuration defaultConfig; private String domainAuthorityNode; int port; boolean debug; public XMPPClient(ResourceBundle configutationBundle) { defaultConfig = new Configuration(configutationBundle); loadDefaultConfig(); } public void register(String[] elementNames, String[] namespaces, final Callback callback) { Log.d(LOG_TAG, "register"); // for (String element : elementNames) { // Log.d(LOG_TAG, "register element name: " + element); // } // for (String namespace : namespaces) { // Log.d(LOG_TAG, "register namespace: " + namespace); // } for(int i=0; i<elementNames.length; i++) { for(int j=0; j<namespaces.length; j++) { providerRegistrar.register(new ProviderElementNamespaceRegistrar.ElementNamespaceTuple(elementNames[i], namespaces[j])); ProviderManager.getInstance().addIQProvider(elementNames[i], namespaces[j], rawXmlProvider); ProviderManager.getInstance().addExtensionProvider(elementNames[i], namespaces[j], rawXmlProvider); } } try { connect(); connection.addPacketListener( new PacketListener() { // TODO remove packet listener on unregister public void processPacket(Packet packet) { callback.receiveMessage(packet.toXML()); } }, new AndFilter(new PacketTypeFilter(Message.class), new NamespaceFilter(namespaces))); } catch (XMPPException e) { Log.e(LOG_TAG, e.getMessage(), e); throw new RuntimeException(e.getMessage()); } } public void unregister(String[] elementNames, String[] namespaces) { Log.d(LOG_TAG, "unregister"); // for (String element : elementNames) { // Log.d(LOG_TAG, "unregister element name: " + element); // } // for (String namespace : namespaces) { // Log.d(LOG_TAG, "unregister namespace: " + namespace); // } for(int i=0; i<elementNames.length; i++) { for(int j=0; j<namespaces.length; j++) { ProviderElementNamespaceRegistrar.ElementNamespaceTuple tuple = new ProviderElementNamespaceRegistrar.ElementNamespaceTuple(elementNames[i], namespaces[j]); providerRegistrar.unregister(tuple); if(!providerRegistrar.isRegistered(tuple)) { removeProviders(tuple); } } } disconnect(); } public boolean UnRegisterCommManager() { Log.d(LOG_TAG, "UnRegisterCommManager"); Set<ProviderElementNamespaceRegistrar.ElementNamespaceTuple> tuples = providerRegistrar.getRegists(); for(ProviderElementNamespaceRegistrar.ElementNamespaceTuple tuple:tuples) { removeProviders(tuple); } providerRegistrar.clear(); return true; } private void removeProviders(ProviderElementNamespaceRegistrar.ElementNamespaceTuple tuple) { Log.d(LOG_TAG, "removeProviders"); ProviderManager.getInstance().removeIQProvider(tuple.elementName, tuple.namespace); ProviderManager.getInstance().removeExtensionProvider(tuple.elementName, tuple.namespace); } public void sendMessage(String messageXml) { // Log.d(LOG_TAG, "sendMessage xml: " + messageXml); try { connect(); connection.sendPacket(createPacketFromXml(messageXml)); disconnect(); } catch (Exception e) { Log.d(LOG_TAG, e.getMessage(), e); throw new RuntimeException(e.getMessage()); } } public void sendIQ(String xml, final Callback callback) { // Log.d(LOG_TAG, "sendIQ xml: " + xml); try { connect(); PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { IQ iq = (IQ)packet; connection.removePacketListener(this); disconnect(); if(iq.getType() == IQ.Type.RESULT) { callback.receiveResult(packet.toXML()); } else if(iq.getType() == IQ.Type.ERROR) { callback.receiveError(packet.toXML()); } } }; String id = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(xml))).getDocumentElement().getAttribute("id"); if(id.equals("")) throw new NullPointerException("IQ XML has no ID attribute!"); connection.addPacketListener(packetListener, new AndFilter(new PacketTypeFilter(IQ.class),new PacketIDFilter(id))); connection.sendPacket(createPacketFromXml(xml)); } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); throw new RuntimeException(e.getMessage()); } } public String getIdentity() { Log.d(LOG_TAG, "getIdentity"); try { connect(); String identity = connection.getUser(); disconnect(); Log.d(LOG_TAG, "getIdentity identity: " + identity); return identity; } catch (XMPPException e) { Log.e(LOG_TAG, e.getMessage(), e); throw new RuntimeException(e.getMessage()); } } public String getDomainAuthorityNode() { Log.d(LOG_TAG, "getDomainAuthorityNode"); return domainAuthorityNode; } public String getItems(String entity, String node, final Callback callback) throws CommunicationException { Log.d(LOG_TAG, "getItems entity: " + entity); try { connect(); DiscoverItems discoItems = new DiscoverItems(); discoItems.setTo(entity); discoItems.setNode(node); PacketListener packetListener = new PacketListener() { public void processPacket(Packet packet) { IQ iq = (IQ)packet; connection.removePacketListener(this); disconnect(); try { if(iq.getType() == IQ.Type.RESULT) { if(isDiscoItem(iq)) callback.receiveItems(packet.toXML()); else callback.receiveResult(packet.toXML()); } else if(iq.getType() == IQ.Type.ERROR) { callback.receiveError(packet.toXML()); } } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); } } }; connection.addPacketListener(packetListener, new AndFilter(new PacketTypeFilter(IQ.class),new PacketIDFilter(discoItems.getPacketID()))); connection.sendPacket(discoItems); return discoItems.getPacketID(); } catch (XMPPException e) { Log.e(LOG_TAG, e.getMessage(), e); throw new CommunicationException(e.getMessage()); } } private void connect() throws XMPPException { Log.d(LOG_TAG, "connect"); if(!connection.isConnected()) { connection.connect(); connection.login(username, password, resource); } usingConnectionCounter++; } private void disconnect() { Log.d(LOG_TAG, "disconnect"); usingConnectionCounter--; if(usingConnectionCounter == 0) connection.disconnect(); } private Packet createPacketFromXml(final String xml) { // Log.d(LOG_TAG, "createPacketFromXml xml: " + xml); return new Packet() { @Override public String toXML() { return xml; } }; } private boolean isDiscoItem(IQ iq) throws SAXException, IOException, ParserConfigurationException { Log.d(LOG_TAG, "isDiscoItem"); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); Element element = factory.newDocumentBuilder().parse(new InputSource(new StringReader(iq.toXML()))).getDocumentElement(); if(element.getChildNodes().getLength() == 1) { Node query = element.getChildNodes().item(0); return query.getNodeName().equals("query") && query.lookupNamespaceURI(query.getPrefix()).equals(XMPPNode.ITEM_NAMESPACE); } else return false; } private static class NamespaceFilter implements PacketFilter { private List<String> namespaces; public NamespaceFilter(String[] namespaces) { this.namespaces = Arrays.asList(namespaces); } public boolean accept(Packet packet) { try { 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") && namespaces.contains(namespace)) return true; } } } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); } return false; } } public Boolean isConnected() { Log.d(LOG_TAG, "isConnected + connected: " + connection.isConnected() ); return connection.isConnected(); } public String newMainIdentity(String identifier, String domain, String password) throws CommunicationException { // TODO this takes no credentials in a private/public key case Log.d(LOG_TAG, "newMainIdentity identity: " + identifier + " domain: " + domain + " password: " + password); String serverHost = domain; int port = defaultConfig.getPort(); String serviceName = domain; try { if(connection.isConnected() && connection.getHost().equals(serverHost) && connection.getPort()==port && connection.getServiceName().equals(serviceName)) { createAccount(connection, identifier, password); } else { ConnectionConfiguration config = new ConnectionConfiguration(domain, port, domain); Connection newIdConnection = new XMPPConnection(config); newIdConnection.connect(); createAccount(newIdConnection, identifier, password); newIdConnection.disconnect(); } } catch (XMPPException e) { Log.e(LOG_TAG, e.getMessage(), e); throw new CommunicationException(e.getMessage()); } return username(identifier, domain) + "/" + resource; } private void createAccount(Connection connection, String username, String password) throws XMPPException { Log.d(LOG_TAG, "createAccount user: " + username + " password: " + password); AccountManager accountMgr = connection.getAccountManager(); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("username", username); attributes.put("password", password); accountMgr.createAccount(username, password, attributes); } public String login(String identifier, String domain, String password) { return login(identifier, domain, password, null); } public String login(String identifier, String domain, String password, String host) { Log.d(LOG_TAG, "login identifier: " + identifier + " domain: " + domain + " password: " + password + " host: " + host); if(isConnected()) logout(); String username = username(identifier, domain); loadConfig(domain, username, password, host); try { connect(); return username + "/" + resource; } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); return null; } } public String loginFromConfig() { Log.d(LOG_TAG, "loginFromConfig"); if(isConnected()) logout(); loadDefaultConfig(); try { connect(); return username + "/" + resource; } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); return null; } } private String username(String identifier, String domain) { Log.d(LOG_TAG, "username identifier: " + identifier + " domain: " + domain); return identifier + "@" + domain; } public boolean logout() { Log.d(LOG_TAG, "logout"); UnRegisterCommManager(); connection.disconnect(); usingConnectionCounter = 0; return true; } public boolean destroyMainIdentity() { Log.d(LOG_TAG, "destroyMainIdentity"); return false; // http://code.google.com/p/asmack/issues/detail?id=63 // try { // connection.getAccountManager().deleteAccount(); // return true; // } catch (Exception e) { // Log.e(LOG_TAG, e.getMessage(), e); // return false; // } } private void loadDefaultConfig() { Log.d(LOG_TAG, "loadDefaultConfig"); domainAuthorityNode = defaultConfig.getDomainAuthorityNode(); port = defaultConfig.getPort(); resource = defaultConfig.getResource(); debug = defaultConfig.getDebug(); loadConfig(defaultConfig.getServer(), defaultConfig.getUsername(), defaultConfig.getPassword(), defaultConfig.getHost()); } private void loadConfig(String serviceName, String username, String password, String host) { Log.d(LOG_TAG, "loadConfig serviceName: " + serviceName + " username: " + username + " password: " + password + " host: " + host); if(host == null) host = serviceName; this.username = username; this.password = password; ConnectionConfiguration config = new ConnectionConfiguration(host, port, serviceName); connection = new XMPPConnection(config); if(debug) { connection.addPacketListener(new PacketListener() { public void processPacket(Packet packet) { // Log.d(LOG_TAG, "Packet received: " + packet.toXML()); } }, new PacketFilter() { public boolean accept(Packet packet) { return true; } }); connection.addPacketSendingListener(new PacketListener() { public void processPacket(Packet packet) { // Log.d(LOG_TAG, "Packet sent: " + packet.toXML()); } }, new PacketFilter() { public boolean accept(Packet packet) { return true; } }); } } public void setDomainAuthorityNode(String domainAuthorityNode) { this.domainAuthorityNode = domainAuthorityNode; } public void setPortNumber(Integer port) { this.port = port; } public void setResource(String resource) { this.resource = resource; } public void setDebug(Boolean enabled) { this.debug = enabled; } }