Java tutorial
/* * Copyright (c) 2011-2014, MOBICAGE NV * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Mobicage NV. * 4. Neither the name of the Mobicage NV nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY MOBICAGE NV ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL MOBICAGE NV BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @@license_version:1.7@@ */ package com.mobicage.rogerthat.xmpp; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.bind.DatatypeConverter; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketFilter; 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.json.simple.JSONObject; import org.json.simple.JSONValue; import com.mobicage.rogerthat.CallbackApiReceiver; import com.mobicage.rogerthat.CallbackDedup; import com.mobicage.rogerthat.CallbackProcessor; import com.mobicage.rogerthat.callbacks.RequestContext; public class CallBackApiXMPPListener implements CallbackApiReceiver { private static final String ROGERTHAT_CALLBACK_BOT = "bot@callback.rogerth.at"; private static final Logger log = Logger.getLogger(CallBackApiXMPPListener.class.getName()); static { ProviderManager.getInstance().addExtensionProvider(CallbackRequestExtension.CALL, CallbackRequestExtension.MOBICAGE_COMM, new CallbackRequestExtensionProvider()); SmackConfiguration.setKeepAliveInterval(30); } private volatile String xmppService; private volatile String xmppUsername; private volatile String xmppPassword; private volatile String sik; private boolean logTraffic = false; private volatile CallbackProcessor processor = new CallbackProcessor(); private XMPPConnection conn; private final LinkedBlockingQueue<Runnable> tasks = new LinkedBlockingQueue<Runnable>(); private volatile XmppConnectionStatus status = XmppConnectionStatus.Initial; private volatile String statusLine = ""; private volatile Thread connectionThread = null; private volatile Runnable onConnected = null; private volatile Runnable onConnectionFailed = null; private volatile Runnable onAuthenticated = null; private volatile Runnable onAuthenticationFailed = null; private volatile CallbackDedup callbackDedup; /** * Set a callback de-duplicator * * @param callbackDedup */ @Override public void setCallbackDedup(CallbackDedup callbackDedup) { this.callbackDedup = callbackDedup; } /** * XMPP host setup to allow federating with rogerth.at XMPP servers. Eg: "jabber.org" if the used XMPP account is * john.doe@jabber.org * * @param xmppService */ public void setXmppService(String xmppService) { this.xmppService = xmppService; } /** * XMPP username. Eg: "john.doe" if the used XMPP account is john.doe@jabber.org * * @param xmppUsername */ public void setXmppUsername(String xmppUsername) { this.xmppUsername = xmppUsername; } /** * XMPP password. * * @param xmppPassword */ public void setXmppPassword(String xmppPassword) { this.xmppPassword = xmppPassword; } /** * Rogerthat Service Identifier Key. See Rogerthat service configuration. * * @param sik */ @Override public void setSikKey(String sik) { this.sik = sik; } /** * Sets whether traffic to and from Rogerthat should be logged. * * @param logTraffic */ @Override public void setLogTraffic(boolean logTraffic) { this.logTraffic = logTraffic; } /** * Runnable executed if a connection with the XmppService has been established. Note: connected does not mean * authenticated. * * @param onConnected */ public void setOnConnected(Runnable onConnected) { this.onConnected = onConnected; } /** * Runnable executed if no connection can be made. * * @param onConnectionFailed */ public void setOnConnectionFailed(Runnable onConnectionFailed) { this.onConnectionFailed = onConnectionFailed; } /** * Runnable executed if the established connection is authenticated. * * @param onAuthenticated */ public void setOnAuthenticated(Runnable onAuthenticated) { this.onAuthenticated = onAuthenticated; } /** * Runnable executed if the established connection cannot be authenticated. After this, the connection to the * XmppService will be dropped. * * @param onAuthenticationFailed */ public void setOnAuthenticationFailed(Runnable onAuthenticationFailed) { this.onAuthenticationFailed = onAuthenticationFailed; } /** * Status of the XMPP connection. * * @return the status of the XMPP connection. */ public XmppConnectionStatus getStatus() { return status; } /** * Error description of the current status. * * @return the error description of the current status. */ public String getStatusLine() { return statusLine; } /** * Disconnect the XMPP connection and stop listening for Rogerthat API callbacks. * * @throws InterruptedException */ public void stopListening() throws InterruptedException { if (connectionThread == null) return; tasks.add(new Runnable() { @Override public void run() { throw new StopListeningException(); } }); } /** * Establish an XMPP connection to XmppService and listen for Rogerthat API callbacks. */ public void startListening() { if (connectionThread != null) { throw new RuntimeException("Previous connection has not yet been closed!"); } if (xmppUsername == null || xmppService == null || xmppPassword == null || sik == null) throw new RuntimeException("Not enough information present to setup an xmpp connection"); final ConnectionListener connectionListener = new ConnectionListener() { @Override public void reconnectionSuccessful() { log.info("Reconnection to jabber server succeeded."); status = XmppConnectionStatus.Connected; } @Override public void reconnectionFailed(Exception e) { log.info("Reconnection to jabber server failed."); } @Override public void reconnectingIn(int seconds) { log.info("Reconnecting to jabber in " + seconds + " seconds ..."); status = XmppConnectionStatus.Reconnecting; } @Override public void connectionClosedOnError(Exception e) { log.info("Connection closed to jabber due to " + e.toString()); } @Override public void connectionClosed() { log.info("Connection to jabber closed."); } }; tasks.clear(); connectionThread = new Thread(new Runnable() { @Override public void run() { try { while (true) { Runnable task = tasks.take(); task.run(); } } catch (StopListeningException e) { disconnect(connectionListener); status = XmppConnectionStatus.Closed; statusLine = ""; } catch (Throwable e) { disconnect(connectionListener); status = XmppConnectionStatus.Closed; statusLine = "Connection interrupted."; } finally { connectionThread = null; } } }); connectionThread.setName("Rogerthat callback listener"); connectionThread.setDaemon(true); connectionThread.start(); tasks.add(new Runnable() { @Override public void run() { ConnectionConfiguration conf = new ConnectionConfiguration(xmppService); status = XmppConnectionStatus.Connecting; log.info("Connecting to jabber server ..."); conn = new XMPPConnection(conf); try { conn.connect(); } catch (XMPPException e) { status = XmppConnectionStatus.ConnectionFailed; statusLine = "Failed to reach Rogerthat servers.\n" + e.getMessage(); conn = null; connectionThread = null; if (onConnectionFailed != null) try { onConnectionFailed.run(); } catch (Throwable t) { log.log(Level.WARNING, "Failure in onConnectionFailed handler.", t); } throw new RuntimeException(e); // Stop thread. } if (onConnected != null) try { onConnected.run(); } catch (Throwable t) { log.log(Level.WARNING, "Failure in onConnected handler.", t); } conn.addConnectionListener(connectionListener); SASLAuthentication.supportSASLMechanism("PLAIN", 0); PacketFilter filter = new PacketFilter() { @Override public boolean accept(Packet packet) { boolean accept = packet instanceof Message && ROGERTHAT_CALLBACK_BOT.equals(packet.getFrom()); if (!accept) log.info("Dropping packet:\n" + packet.toXML()); return accept; } }; conn.addPacketListener(new PacketListener() { @Override public void processPacket(Packet packet) { log.info("Processing packet:\n" + packet.toXML()); if (!(packet instanceof Message)) { log.info("Ignoring non message packet."); return; } Message message = (Message) packet; PacketExtension extension = packet.getExtension("call", "mobicage:comm"); if (extension == null || !(extension instanceof CallbackRequestExtension)) { log.info("Ignoring incomplete packet."); return; } CallbackRequestExtension call = (CallbackRequestExtension) extension; if (!sik.equals(call.getSik())) { log.info("Ignoring packet with incorrect sik."); return; } String json; try { json = new String(DatatypeConverter.parseBase64Binary(call.getBase64Body()), "UTF-8"); } catch (UnsupportedEncodingException e) { log.log(Level.WARNING, "Could not decode base64 packet.", e); return; } final JSONObject request = (JSONObject) JSONValue.parse(json); if (logTraffic) log.info(String.format("Incoming Rogerthat API Callback.\nSIK: %s\n\n%s", sik, json)); final String id = (String) request.get("id"); if (callbackDedup != null) { byte[] response = callbackDedup.getResponse(id); if (response != null) { Message resultMessage = new Message(message.getFrom()); resultMessage.setFrom(message.getTo()); resultMessage.addExtension(new CallbackResponseExtension(sik, DatatypeConverter.printBase64Binary(response))); log.info("Sending message:\n" + resultMessage.toXML()); conn.sendPacket(resultMessage); return; } } final JSONObject result = new JSONObject(); final RequestContext requestContext = new RequestContext(id, sik); try { processor.process(request, result, requestContext); } finally { try { StringWriter writer = new StringWriter(); try { result.writeJSONString(writer); writer.flush(); json = writer.toString(); if (logTraffic) log.info("Returning result:\n" + json); } finally { writer.close(); } } catch (IOException e) { log.log(Level.SEVERE, "Could not write json object to string", e); return; } Message resultMessage = new Message(message.getFrom()); resultMessage.setFrom(message.getTo()); try { byte[] response = json.getBytes("UTF-8"); resultMessage.addExtension(new CallbackResponseExtension(sik, DatatypeConverter.printBase64Binary(response))); if (callbackDedup != null) { callbackDedup.storeResponse(id, response); } } catch (UnsupportedEncodingException e) { log.log(Level.SEVERE, "Could not add result to message packet", e); return; } log.info("Sending message:\n" + resultMessage.toXML()); conn.sendPacket(resultMessage); } } }, filter); try { conn.login(xmppUsername, xmppPassword); } catch (XMPPException e1) { status = XmppConnectionStatus.ConnectionFailed; statusLine = "Failed to authenticate jabber connection. Verify your configuration.\n" + e1.getMessage(); conn = null; connectionThread = null; if (onAuthenticationFailed != null) try { onAuthenticationFailed.run(); } catch (Throwable t) { log.log(Level.WARNING, "Failure in onAuthenticationFailed handler.", t); } throw new RuntimeException(); // Stop thread. } status = XmppConnectionStatus.Connected; if (onAuthenticated != null) try { onAuthenticated.run(); } catch (Throwable t) { log.log(Level.WARNING, "Failure in onAuthenticated handler.", t); } } }); } /** * Disconnects xmpp connection. Should only be called from connectionThread. */ private void disconnect(ConnectionListener connectionListener) { if (conn == null) return; conn.removeConnectionListener(connectionListener); conn.disconnect(); conn = null; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.test.TestHandler handler) { processor.testTestHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.messaging.UpdateHandler handler) { processor.messagingUpdateHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.messaging.PokeHandler handler) { processor.messagingPokeHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.messaging.FormUpdateHandler handler) { processor.messagingFormUpdateHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.messaging.FlowMemberResultHandler handler) { processor.messagingFlowMemberResultHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.friend.InviteResultHandler handler) { processor.friendInviteResultHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.friend.InvitedHandler handler) { processor.friendInvitedHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.friend.BrokeUpHandler handler) { processor.friendBrokeUpHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.friend.InReachHandler handler) { processor.friendInReachHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.friend.OutOfReachHandler handler) { processor.friendOutOfReachHandler = handler; } @Override public void subscribe(com.mobicage.rogerthat.callbacks.system.ApiCallHandler handler) { processor.systemApiCallHandler = handler; } }