package org.myrobotlab.service; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.chat.Chat; import org.jivesoftware.smack.chat.ChatManager; import org.jivesoftware.smack.chat.ChatManagerListener; import org.jivesoftware.smack.chat.ChatMessageListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence.Type; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.Roster.SubscriptionMode; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterListener; import org.jivesoftware.smack.roster.packet.RosterPacket.ItemStatus; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration.Builder; import org.myrobotlab.codec.CodecCli; import org.myrobotlab.codec.CodecUri; import org.myrobotlab.codec.CodecUtils; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.framework.Status; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.Connection; import org.myrobotlab.service.interfaces.Gateway; import org.myrobotlab.service.interfaces.ServiceInterface; import org.slf4j.Logger; /** * An Xmpp service which utilizes Jive's smack client library There is smack, * whack, and tinder * http://stackoverflow.com/questions/1547599/differences-between-smack-tinder- * and-whack * * @author GROG * */ public class Xmpp extends Service implements Gateway, ChatManagerListener, ChatMessageListener, MessageListener, RosterListener, ConnectionListener {// , public static class Contact { public String user; public String presence; public String type; public String name; public String status; public String toString() { return String.format("user: %s, name: %s, presence: %s, type: %s, status: %s", user, name, type, presence, status); } } public static class XmppMsg { public String from; public String msg; public String type; public String stanzaId; public XmppMsg(Chat chat, Message msg) { this.from = chat.getParticipant(); this.msg = msg.getBody(); Message.Type t = msg.getType(); if (t != null) { this.type = msg.getType().toString(); } stanzaId = msg.getStanzaId(); } } boolean isConnected = false; private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(Xmpp.class); /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(Xmpp.class.getCanonicalName()); meta.addDescription("xmpp service to access the jabber network"); meta.addCategory("connectivity"); meta.addDependency("org.jivesoftware.smack", "4.1.6"); return meta; } transient CodecCli cli = new CodecCli(); transient CodecUri uri = new CodecUri(); TreeMap<String, Contact> contacts = new TreeMap<String, Contact>(); String username; String password; String hostname = "myrobotlab.org"; // talk.myrobotlab.org String serviceName = "myrobotlab.org"; // xmpp.myrobotlab.org int port = 5222; transient XMPPTCPConnectionConfiguration config; transient XMPPTCPConnection connection; transient ChatManager chatManager; transient Roster roster = null; /** * auditors chat buddies who can see what commands are being processed and by * who through the Xmpp service TODO - audit full system ??? regardless of * message origin? */ HashSet<String> auditors = new HashSet<String>(); // HashSet<String> responseRelays = new HashSet<String>(); HashSet<String> allowCommandsFrom = new HashSet<String>(); transient HashMap<String, Chat> chats = new HashMap<String, Chat>(); transient Chat chat = null; public Xmpp(String n) { super(n); } public void addBuddy(String user) throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException { Roster roster = Roster.getInstanceFor(connection); roster.setSubscriptionMode(SubscriptionMode.accept_all); // jid: String, user: String, groups: String[] // null groups roster.createEntry("grog@myrobotlab.org", "grog", null); } @Override public void addConnectionListener(String name) { // TODO Auto-generated method stub } public void addXmppMsgListener(Service service) { // FIXME - implement direct callback or pub sub support ?? } public void broadcast(String msg) { // TODO - possibly implement // but we should use more xmpp definitions e.g. broadcast to room // define a room etc... } public void chatCreated(Chat chat, boolean locallyCreated) { // test if locallyCreated if (!locallyCreated) { chat.addMessageListener(this); } } // grog@xmpp://{host}:5222 ??? @Override public void connect(String uri) throws URISyntaxException { // TODO Auto-generated method stub } public void connect(String hostname, int port, String username, String password) throws Exception { purgeTask("reconnect"); this.hostname = hostname; this.serviceName = hostname; this.port = port; this.username = username; this.password = password; Builder builder = XMPPTCPConnectionConfiguration.builder(); builder.setUsernameAndPassword(username, password); builder.setServiceName(serviceName); builder.setServiceName(hostname); builder.setPort(port); builder.setSecurityMode(SecurityMode.disabled); builder.setDebuggerEnabled(true); XMPPTCPConnectionConfiguration config = builder.build(); connection = new XMPPTCPConnection(config); connection.connect().login(); roster = Roster.getInstanceFor(connection); chatManager = ChatManager.getInstanceFor(connection); roster.addRosterListener(this); isConnected = true; // not worth it - always empty right after connect // getContactList(); broadcastState(); } public void connect(String username, String password) throws Exception { connect("myrobotlab.org", 5222, username, password); } public void disconnect() { if (connection != null) { connection.disconnect(); } isConnected = false; broadcastState(); } @Override public void entriesAdded(Collection<String> entries) { log.info("entriesAdded {}", entries); getContactList(); } @Override public void entriesDeleted(Collection<String> entries) { log.info("entriesAdded {}", entries); } @Override public void entriesUpdated(Collection<String> entries) { log.info("entriesAdded {}", entries); } @Override public HashMap<URI, Connection> getClients() { // TODO Auto-generated method stub return null; } @Override public List<Connection> getConnections(URI clientKey) { // TODO Auto-generated method stub return null; } public Contact getContact(RosterEntry r) { Contact contact = new Contact(); contact.name = r.getName(); contact.user = r.getUser(); contact.type = r.getType().toString(); Type presenceType = roster.getPresence(r.getUser()).getType(); if (presenceType != null) { contact.presence = presenceType.toString(); } ItemStatus status = r.getStatus(); if (status != null) { contact.status = status.toString(); } // ItemStatus status = r.getStatus(); // null // log.info("roster entry {}", r.toString()); // contact.na= r.getUser(); log.info("getContact {}", contact.toString()); return contact; } /** * Displays users (entries) in the roster */ public Map<String, Contact> getContactList() { // Roster roster = Roster.getInstanceFor(connection); Collection<RosterEntry> entries = roster.getEntries(); contacts.clear(); log.info("\n\n" + entries.size() + " buddy(ies):"); for (RosterEntry r : entries) { Contact c = getContact(r); contacts.put(c.user, c); } broadcastState(); return contacts; } @Override public String getPrefix(URI protocolKey) { // TODO Auto-generated method stub return null; } @Override public void presenceChanged(Presence presence) { log.info("presenceChanged {}", presence); // String user = presence.getFrom(); getContactList(); /* * if (contacts.containsKey(user)) { Contact c = contacts.get(user); * c.presence = presence.toString(); invoke("publishPresenceChanged", c); } */ } /** * Process received messages */ public void processMessage(Chat chat, Message message) { XmppMsg xmppMsg = new XmppMsg(chat, message); invoke("publishXmppMsg", xmppMsg); Message.Type type = message.getType(); String participant = chat.getParticipant(); String body = message.getBody(); log.info("message of type {} from user {} - {}", type, participant, body); if (type == Message.Type.chat) { if (body.startsWith("/")) { // String pathInfo = String.format("/%s/service%s", // CodecUtils.PREFIX_API, body); FIXME - wow that was horrific String pathInfo = String.format("/%s%s", CodecUtils.PREFIX_API, body); try { org.myrobotlab.framework.Message msg = CodecUri.decodePathInfo(pathInfo); Object ret = null; ServiceInterface si = Runtime.getService(msg.name); if (si == null) { ret = Status.error("could not find service %s", msg.name); } else { ret = si.invoke(msg.method, msg.data); } if (ret != null && ret instanceof Serializable) { // configurable use log or system.out ? // FIXME - make getInstance configurable // Encoder // reference !!! sendMessage(CodecUtils.toJson(ret), participant); } } catch (Exception e) { try { Logging.logError(e); sendMessage(e.toString(), participant); } catch (Exception e2) { // give up } } } } else { log.error("don't know how to handle message of type {}", type); } } @Override public void processMessage(Message msg) { log.info("here"); } @Override public Connection publishConnect(Connection keys) { // TODO Auto-generated method stub return null; } public Contact publishPresenceChanged(Contact contact) { return contact; } /** * MRL Interface to gateways .. onMsg(GatewayData d) addMsgListener(Service s) * publishMsg(Object..) returns gateway specific data */ // FIXME - should be MessageXmpp along with all other message types under // org.myrobotlab.msg public XmppMsg publishXmppMsg(XmppMsg msg) { return msg; } public XmppMsg publishSentXmppMsg(XmppMsg msg) { return msg; } /** * Sends the specified text as a message to the other chat participant. * * @param message * @param to * @throws XMPPException * @throws NotConnectedException */ public void sendMessage(String text, String to) throws XMPPException, NotConnectedException { if (chat == null) { Chat chat = chatManager.createChat(to, this); chat.addMessageListener(this); this.chat = chat; } Message message = new Message(); message.setTo(chat.getParticipant()); message.setType(Message.Type.chat); // Message.Type.groupchat message.setThread(chat.getThreadID()); message.setBody(text); chat.sendMessage(message); invoke("publishSentXmppMsg", new XmppMsg(chat, message)); } @Override public void sendRemote(String key, org.myrobotlab.framework.Message msg) throws URISyntaxException { // TODO Auto-generated method stub } @Override public void sendRemote(URI key, org.myrobotlab.framework.Message msg) { // TODO Auto-generated method stub } public void setStatus(boolean available, String status) { if (connection != null && connection.isConnected()) { Presence.Type type = available ? Type.available : Type.unavailable; Presence presence = new Presence(type); presence.setStatus(status); // connection.sendPacket(presence); } else { log.error("setStatus not connected"); } } @Override public void stopService() { super.stopService(); disconnect(); } // FIXME - sendMsg onMsg getMsg - GLOBAL INTERFACE FOR GATEWAYS // FIXME - handle multiple user accounts // best -> // https://www.snip2code.com/Snippet/828300/Smack-API-example-(uses-Smack-v4-1-5) public static void main(String[] args) { // 1. get connection // 2. login // 3. set auto accept // 4. get roster // 5. list buddies // 6. addChatListener (add self) // 7. in chatCreated // create a Message Listener (add self) // more stuff LoggingFactory.init(Level.INFO); try { // SmackConfiguration.DEBUG = true; Xmpp xmpp1 = (Xmpp) Runtime.createAndStart("xmpp", "Xmpp"); // Runtime.start(String.format("clock%d", i), "Clock"); // Runtime.start("gui", "GUIService"); // Runtime.start("python", "Python"); // HMMM is fully qualified name important ??? // grog.robot01@myrobotlab.org vs grog.robot01 ??? // xmpp1.connect("grog.robot01@myrobotlab.org", "xxxxxx"); // xmpp1.connect("myrobotlab.org", 5222, // "grog.robot01@myrobotlab.org", "zardoz7"); // xmpp1.test(); // xmpp1.test2(); // xmpp1.addBuddy("grog@myrobotlab.org"); // for (int i = 0; i < 100; ++i) { // xmpp1.sendMessage(String.format("/runtime/getUptime/%d", i), // "grog@myrobotlab.org"); // } Runtime.createAndStart("webgui", "WebGui"); xmpp1.sendMessage("hello !", "grog@myrobotlab.org"); xmpp1.getContactList(); // xmpp1.getContactList(); // xmpp1.connect("myrobotlab.org", 5222, "grog.robot01", "xxxxxxx"); // xmpp1.addAuditor("Ma. Vo."); // xmpp1.sendMessage("Ma. Vo. - xmpp test", "Ma. Vo."); // xmpp1.send("Ma. Vo.", "xmpp test"); // xmpp1.sendMessage("hello from incubator by name " + // System.currentTimeMillis(), "Greg Perry"); xmpp1.sendMessage("/runtime/getUptime", "GroG@myrobotlab.org"); xmpp1.sendMessage("msg 2", "GroG@myrobotlab.org"); // xmpp1.sendMessage("/runtime", "grogbot@myrobotlab.org"); // TEST CASES : // 1. different clients // 2. group chats // 3. URL processing must be handled in Codec !!!! and same as CLI // !!! // 4. Non-text chats ? // 5. Mrl Messages log.info("here"); } catch (Exception e) { Logging.logError(e); } } @Override public void authenticated(XMPPConnection arg0, boolean arg1) { // TODO Auto-generated method stub } @Override public void connected(XMPPConnection arg0) { // TODO Auto-generated method stub } @Override public void connectionClosed() { log.info("connectionClosed"); addTask("reconnect", 5000, "connect", hostname, port, username, password); isConnected = false; broadcastState(); } @Override public void connectionClosedOnError(Exception arg0) { // TODO Auto-generated method stub } @Override public void reconnectingIn(int arg0) { // TODO Auto-generated method stub } @Override public void reconnectionFailed(Exception arg0) { // TODO Auto-generated method stub } @Override public void reconnectionSuccessful() { // TODO Auto-generated method stub } @Override public String publishConnect() { // TODO Auto-generated method stub return null; } @Override public String publishDisconnect() { // TODO Auto-generated method stub return null; } @Override public Status publishError() { // TODO Auto-generated method stub return null; } }