/* * Copyright 2006-2010 Daniel Henninger. All rights reserved. * * This software is published under the terms of the GNU Public License (GPL), * a copy of which is included in this distribution. */ package net.sf.kraken.protocols.xmpp; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Date; import java.util.Collection; import net.sf.kraken.BaseTransport; import net.sf.kraken.protocols.xmpp.packet.AttentionExtension; import net.sf.kraken.protocols.xmpp.packet.GoogleMailBoxPacket; import net.sf.kraken.protocols.xmpp.packet.GoogleMailNotifyExtension; import net.sf.kraken.protocols.xmpp.packet.GoogleMailSender; import net.sf.kraken.protocols.xmpp.packet.GoogleMailThread; import net.sf.kraken.protocols.xmpp.packet.GoogleNewMailExtension; import net.sf.kraken.protocols.xmpp.packet.IQWithPacketExtension; import net.sf.kraken.protocols.xmpp.packet.ProbePacket; import net.sf.kraken.type.ChatStateType; import net.sf.kraken.type.ConnectionFailureReason; import net.sf.kraken.type.NameSpace; import net.sf.kraken.type.TransportType; import org.apache.log4j.Logger; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManagerListener; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smack.packet.Message.Type; import org.jivesoftware.smack.RosterEntry; import org.jivesoftware.smack.RosterListener; import org.jivesoftware.smackx.packet.ChatStateExtension; import org.jivesoftware.smackx.packet.DelayInformation; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.LocaleUtils; import org.xmpp.packet.JID; import org.xmpp.packet.Message; /** * Handles incoming events from XMPP server. * * @author Daniel Henninger * @author Mehmet Ecevit */ public class XMPPListener implements MessageListener, ConnectionListener, ChatManagerListener, PacketListener, RosterListener { static Logger Log = Logger.getLogger(XMPPListener.class); /** * Creates an XMPP listener instance and ties to session. * * @param session Session this listener is associated with. */ public XMPPListener(XMPPSession session) { this.xmppSessionRef = new WeakReference<XMPPSession>(session); } /** * Session instance that the listener is associated with. */ public WeakReference<XMPPSession> xmppSessionRef = null; /** * Last google mail thread id we saw. */ public Long lastGMailThreadId = null; /** * Last google mail thread date we saw. */ public Date lastGMailThreadDate = null; /** * Returns the XMPP session this listener is attached to. * * @return XMPP session we are attached to. */ public XMPPSession getSession() { return xmppSessionRef.get(); } /** * Handles incoming messages. * * @param chat Chat instance this message is associated with. * @param message Message received. */ public void processMessage(Chat chat, org.jivesoftware.smack.packet.Message message) { Log.debug("Received "+getSession().getTransport().getType().name()+" message: "+message.toXML()); try { final BaseTransport<XMPPBuddy> transport = getSession().getTransport(); final JID legacyJID = transport.convertIDToJID(message.getFrom()); final JID localJID = getSession().getJID(); final PacketExtension pe = message.getExtension("x", NameSpace.X_DELAY); final PacketExtension attExt = message.getExtension(AttentionExtension.ELEMENT_NAME, AttentionExtension.NAMESPACE); if (pe != null && pe instanceof DelayInformation) { DelayInformation di = (DelayInformation)pe; transport.sendOfflineMessage( localJID, legacyJID, message.getBody(), di.getStamp(), di.getReason() ); } else if (attExt != null && (attExt instanceof AttentionExtension)) { transport.sendAttentionNotification(localJID, legacyJID, message.getBody()); } else { // see if we got sent chat state notifications final PacketExtension cse = message .getExtension("http://jabber.org/protocol/chatstates"); if (cse != null && cse instanceof ChatStateExtension) { final String chatState = cse.getElementName(); try { final ChatStateType cst = ChatStateType.valueOf( ChatStateType.class, chatState); switch (cst) { case active: // only send a 'stand alone' active state if the // message stanza does not include a text // message. If it does, the 'active' state is // included with that chat message below. if (message.getBody() == null || message.getBody().trim().length() == 0) { transport.sendChatActiveNotification( localJID, legacyJID); } break; case composing: transport.sendComposingNotification(localJID, legacyJID); break; case gone: transport.sendChatGoneNotification(localJID, legacyJID); break; case inactive: transport.sendChatInactiveNotification( localJID, legacyJID); break; case paused: transport.sendComposingPausedNotification( localJID, legacyJID); break; default: Log.debug("Unexpected chat state recieved: " + cst); break; } } catch (IllegalArgumentException ex) { Log.warn("Illegal chat state notification " + "received from legacy domain: " + chatState); } } if (message.getType() == Type.error) { Log.debug("Received an error message! Message: " + message.toXML()); transport.sendMessage(localJID, legacyJID, message.getBody(), Message.Type.error); } else { transport.sendMessage(localJID, legacyJID, message.getBody()); } } // if (message.getProperty("time") == null || message.getProperty("time").equals("")) { // } // else { // getSession().getTransport().sendOfflineMessage( // getSession().getJID(), // getSession().getTransport().convertIDToJID(message.getFrom()), // message.getBody(), // Message.Type.chat, // message.getProperty("time").toString() // ); // } } catch (Exception ex) { Log.debug("E001:"+ ex.getMessage(), ex); } } public void connectionClosed() { getSession().sessionDisconnectedNoReconnect(null); } public void connectionClosedOnError(Exception exception) { getSession().setFailureStatus(ConnectionFailureReason.UNKNOWN); getSession().sessionDisconnected(LocaleUtils.getLocalizedString("gateway.xmpp.connectionclosed", "kraken")); } public void reconnectingIn(int i) { //Ignoring for now } public void reconnectionSuccessful() { //Ignoring for now } public void reconnectionFailed(Exception exception) { //Ignoring for now } public void chatCreated(Chat chat, boolean b) { chat.addMessageListener(this); } public void processPacket(Packet packet) { if (packet instanceof GoogleMailBoxPacket) { if (JiveGlobals.getBooleanProperty("plugin.gateway.gtalk.mailnotifications", true)) { GoogleMailBoxPacket mbp = (GoogleMailBoxPacket)packet; this.setLastGMailThreadDate(mbp.getResultTime()); Integer newMailCount = 0; String mailList = ""; for (GoogleMailThread mail : mbp.getMailThreads()) { newMailCount++; if (this.getLastGMailThreadId() == null || mail.getThreadId() > this.getLastGMailThreadId()) { this.setLastGMailThreadId(mail.getThreadId()); } String senderList = ""; for (GoogleMailSender sender : mail.getSenders()) { if (!senderList.equals("")) { senderList += ", "; } String name = sender.getName(); if (name != null) { senderList += name + " <"; } senderList += sender.getAddress(); if (name != null) { senderList += ">"; } } mailList += "\n "+senderList+" sent "+mail.getSubject(); } if (newMailCount > 0) { getSession().getTransport().sendMessage( getSession().getJID(), getSession().getTransport().getJID(), LocaleUtils.getLocalizedString("gateway.gtalk.mail", "kraken", Arrays.asList(newMailCount))+mailList, Message.Type.headline ); } } } else if (packet instanceof IQ) { Log.debug("XMPP: Got google mail notification"); GoogleNewMailExtension gnme = (GoogleNewMailExtension)packet.getExtension(GoogleNewMailExtension.ELEMENT_NAME, GoogleNewMailExtension.NAMESPACE); if (gnme != null) { Log.debug("XMPP: Sending google mail request"); getSession().conn.sendPacket(new IQWithPacketExtension(new GoogleMailNotifyExtension())); } } } public Long getLastGMailThreadId() { return lastGMailThreadId; } public void setLastGMailThreadId(Long lastGMailThreadId) { this.lastGMailThreadId = lastGMailThreadId; } public Date getLastGMailThreadDate() { return lastGMailThreadDate; } public void setLastGMailThreadDate(Date lastGMailThreadDate) { this.lastGMailThreadDate = lastGMailThreadDate; } public void entriesAdded(Collection<String> addresses) { for (String addr : addresses) { RosterEntry entry = getSession().conn.getRoster().getEntry(addr); getSession().getBuddyManager().storeBuddy(new XMPPBuddy(getSession().getBuddyManager(), entry.getUser(), entry.getName(), entry.getGroups(), entry)); // Facebook does not support presence probes in their XMPP implementation. See http://developers.facebook.com/docs/chat#features if (!TransportType.facebook.equals(getSession().getTransport().getType())) { //ProbePacket probe = new ProbePacket(getSession().getJID()+"/"+getSession().xmppResource, entry.getUser()); ProbePacket probe = new ProbePacket(null, entry.getUser()); Log.debug("XMPP: Sending the following probe packet: "+probe.toXML()); try { getSession().conn.sendPacket(probe); } catch (IllegalStateException e) { Log.debug("XMPP: Not connected while trying to send probe."); } } } } public void entriesUpdated(Collection<String> addresses) { // TODO: Check if we need to do something with this later } public void entriesDeleted(Collection<String> addresses) { for (String addr : addresses) { getSession().getBuddyManager().removeBuddy(addr); } } public void presenceChanged(org.jivesoftware.smack.packet.Presence presence) { // Uhm why do we need this given that we have a presence listener } }