blob: 65525f7652ab34fb43020d4d2f3b797a400029dd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 Composent, Inc. and others. All rights reserved. This
* program and the accompanying materials are made available under the terms of
* the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: Composent, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.internal.provider.xmpp.smack;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.ecf.core.ContainerConnectException;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.identity.IDFactory;
import org.eclipse.ecf.core.identity.Namespace;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.internal.provider.xmpp.XmppPlugin;
import org.eclipse.ecf.provider.comm.DisconnectEvent;
import org.eclipse.ecf.provider.comm.IAsynchEventHandler;
import org.eclipse.ecf.provider.comm.IConnectionListener;
import org.eclipse.ecf.provider.comm.ISynchAsynchConnection;
import org.eclipse.ecf.provider.xmpp.identity.XMPPID;
import org.eclipse.ecf.provider.xmpp.identity.XMPPRoomID;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterEntry;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Bind;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Message.Type;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
public class ECFConnection implements ISynchAsynchConnection {
/**
*
*/
private static final String GOOGLE_TALK_HOST = "talk.google.com";
public static final String CLIENT_TYPE = "ecf.";
public static final boolean DEBUG = Boolean.getBoolean("smack.debug");
protected static final String STRING_ENCODING = "UTF-8";
public static final String OBJECT_PROPERTY_NAME = ECFConnection.class
.getName() + ".object";
protected static final int XMPP_DEFAULT_PORT = 5222;
protected static final int XMPPS_DEFAULT_PORT = 5223;
private XMPPConnection connection = null;
private IAsynchEventHandler handler = null;
private boolean isStarted = false;
private int serverPort = -1;
private String serverResource;
private final Map properties = null;
private boolean isConnected = false;
private Namespace namespace = null;
private boolean google = false;
private boolean disconnecting = false;
private int BIND_TIMEOUT = new Integer(System.getProperty(
"org.eclipse.ecf.provider.xmpp.ECFConnection.bindTimeout", "15000"))
.intValue();
private Object bindLock = new Object();
private String jid;
private final PacketListener packetListener = new PacketListener() {
public void processPacket(Packet arg0) {
handlePacket(arg0);
}
};
private final ConnectionListener connectionListener = new ConnectionListener() {
public void connectionClosed() {
handleConnectionClosed(new IOException("Connection reset by peer"));
}
public void connectionClosedOnError(Exception e) {
handleConnectionClosed(e);
}
public void reconnectingIn(int seconds) {
}
public void reconnectionFailed(Exception e) {
}
public void reconnectionSuccessful() {
}
};
protected void logException(String msg, Throwable t) {
XmppPlugin.log(msg, t);
}
public Map getProperties() {
return properties;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
*/
public Object getAdapter(Class adapter) {
if (adapter == null)
return null;
if (adapter.isInstance(this))
return this;
final IAdapterManager adapterManager = XmppPlugin.getDefault()
.getAdapterManager();
return (adapterManager == null) ? null : adapterManager.loadAdapter(
this, adapter.getName());
}
public XMPPConnection getXMPPConnection() {
return connection;
}
public ECFConnection(boolean google, Namespace ns, IAsynchEventHandler h) {
this.handler = h;
this.namespace = ns;
this.google = google;
if (DEBUG)
XMPPConnection.DEBUG_ENABLED = true;
}
protected String getPasswordForObject(Object data) {
String password = null;
try {
password = (String) data;
} catch (final ClassCastException e) {
return null;
}
return password;
}
private XMPPID getXMPPID(ID remote) throws ECFException {
XMPPID jabberID = null;
try {
jabberID = (XMPPID) remote;
} catch (final ClassCastException e) {
throw new ECFException(e);
}
return jabberID;
}
public synchronized Object connect(ID remote, Object data, int timeout)
throws ECFException {
if (connection != null)
throw new ECFException("already connected");
if (timeout > 0)
SmackConfiguration.setPacketReplyTimeout(timeout);
Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
final XMPPID jabberURI = getXMPPID(remote);
String username = jabberURI.getNodename();
String hostname = jabberURI.getHostname();
String hostnameOverride = null;
// Check for the URI form of "joe@bloggs.org;talk.google.com", which
// would at this point would have
// - username = "joe"
// - hostname = "blogs.org;talk.google.com"
// - hostnameOverride = null
//
// We need to turn this into:
// - username = "joe"
// - hostname = "bloggs.org"
// - hostnameOverride = "talk.google.com"
int semiColonIdx = hostname.lastIndexOf(';');
if (semiColonIdx != -1) {
hostnameOverride = hostname.substring(semiColonIdx + 1);
hostname = hostname.substring(0, semiColonIdx);
}
if (google && hostnameOverride == null) {
hostnameOverride = GOOGLE_TALK_HOST;
}
final String serviceName = hostname;
serverPort = jabberURI.getPort();
serverResource = jabberURI.getResourceName();
if (serverResource == null
|| serverResource.equals(XMPPID.PATH_DELIMITER)) {
serverResource = getClientIdentifier();
jabberURI.setResourceName(serverResource);
}
try {
ConnectionConfiguration config;
if (hostnameOverride != null) {
config = new ConnectionConfiguration(hostnameOverride,
XMPP_DEFAULT_PORT, serviceName);
} else if (serverPort == -1) {
config = new ConnectionConfiguration(serviceName);
} else {
config = new ConnectionConfiguration(serviceName, serverPort);
}
config.setSendPresence(true);
connection = new XMPPConnection(config);
connection.connect();
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
if (google || GOOGLE_TALK_HOST.equals(hostnameOverride)) {
username = username + "@" + serviceName;
}
connection.addPacketListener(packetListener, null);
connection.addConnectionListener(connectionListener);
// Login
connection.login(username, (String) data, serverResource);
waitForBindResult();
} catch (final XMPPException e) {
throw new ContainerConnectException("Login attempt failed", e);
}
return jid;
}
private void waitForBindResult() throws XMPPException {
// We'll wait a maximum of
long bindTimeout = System.currentTimeMillis() + BIND_TIMEOUT;
synchronized (bindLock) {
while (jid == null && System.currentTimeMillis() < bindTimeout) {
try {
bindLock.wait(1000);
} catch (InterruptedException e) {
}
}
if (jid == null)
throw new XMPPException(
"timeout waiting for server bind result");
isConnected = true;
}
}
private String getClientIdentifier() {
return CLIENT_TYPE + handler.getEventHandlerID().getName();
}
public void sendPacket(Packet packet) throws XMPPException {
if (connection != null)
connection.sendPacket(packet);
}
public synchronized void disconnect() {
disconnecting = true;
if (isStarted()) {
stop();
}
if (connection != null) {
connection.removePacketListener(packetListener);
connection.removeConnectionListener(connectionListener);
connection.disconnect();
connection = null;
synchronized (bindLock) {
jid = null;
isConnected = false;
}
}
}
public synchronized boolean isConnected() {
return (isConnected);
}
public synchronized ID getLocalID() {
if (!isConnected())
return null;
try {
return IDFactory.getDefault().createID(namespace.getName(),
new Object[] { connection.getConnectionID() });
} catch (final Exception e) {
logException("Exception in getLocalID", e);
return null;
}
}
public synchronized void start() {
if (isStarted())
return;
isStarted = true;
}
public boolean isStarted() {
return isStarted;
}
public synchronized void stop() {
isStarted = false;
}
protected void handleConnectionClosed(Exception e) {
if (!disconnecting) {
disconnecting = true;
handler.handleDisconnectEvent(new DisconnectEvent(this, e, null));
}
}
protected void handlePacket(Packet arg0) {
handleJidPacket(arg0);
try {
final Object val = arg0.getProperty(OBJECT_PROPERTY_NAME);
if (val != null) {
handler.handleAsynchEvent(new ECFConnectionObjectPacketEvent(
this, arg0, val));
} else {
handler.handleAsynchEvent(new ECFConnectionPacketEvent(this,
arg0));
}
} catch (final IOException e) {
logException("Exception in handleAsynchEvent", e);
try {
disconnect();
} catch (final Exception e1) {
logException("Exception in disconnect()", e1);
}
}
}
private void handleJidPacket(Packet packet) {
if (jid != null)
return;
if (packet instanceof IQ) {
IQ iqPacket = (IQ) packet;
if (iqPacket.getType().equals(IQ.Type.RESULT)
&& iqPacket instanceof Bind) {
Bind bindPacket = (Bind) iqPacket;
synchronized (bindLock) {
jid = bindPacket.getJid();
bindLock.notify();
}
}
}
}
public synchronized void sendAsynch(ID receiver, byte[] data)
throws IOException {
if (data == null)
throw new IOException("no data");
final Message aMsg = new Message();
aMsg.setProperty(OBJECT_PROPERTY_NAME, data);
sendMessage(receiver, aMsg);
}
protected void sendMessage(ID receiver, Message aMsg) throws IOException {
synchronized (this) {
if (!isConnected())
throw new IOException("not connected");
try {
if (receiver == null)
throw new IOException(
"receiver cannot be null for xmpp instant messaging");
else if (receiver instanceof XMPPID) {
final XMPPID rcvr = (XMPPID) receiver;
aMsg.setType(Message.Type.chat);
final String receiverName = rcvr.getFQName();
final Chat localChat = connection.getChatManager()
.createChat(receiverName, new MessageListener() {
public void processMessage(Chat chat,
Message message) {
}
});
localChat.sendMessage(aMsg);
} else if (receiver instanceof XMPPRoomID) {
final XMPPRoomID roomID = (XMPPRoomID) receiver;
aMsg.setType(Message.Type.groupchat);
final String to = roomID.getMucString();
aMsg.setTo(to);
connection.sendPacket(aMsg);
} else
throw new IOException(
"receiver must be of type XMPPID or XMPPRoomID");
} catch (final XMPPException e) {
final IOException result = new IOException(
"XMPPException in sendMessage: " + e.getMessage());
result.setStackTrace(e.getStackTrace());
throw result;
}
}
}
public synchronized Object sendSynch(ID receiver, byte[] data)
throws IOException {
if (data == null)
throw new IOException("data cannot be null");
// This is assumed to be disconnect...so we'll just disconnect
// disconnect();
return null;
}
public void addListener(IConnectionListener listener) {
// XXX Not yet implemented
}
public void removeListener(IConnectionListener listener) {
// XXX Not yet implemented
}
public void sendMessage(ID target, String message) throws IOException {
if (target == null)
throw new IOException("target cannot be null");
if (message == null)
throw new IOException("message cannot be null");
final Message aMsg = new Message();
aMsg.setBody(message);
sendMessage(target, aMsg);
}
public static Map getPropertiesFromPacket(Packet packet) {
final Map result = new HashMap();
final Iterator i = packet.getPropertyNames().iterator();
for (; i.hasNext();) {
final String name = (String) i.next();
result.put(name, packet.getProperty(name));
}
return result;
}
public static Packet setPropertiesInPacket(Packet input, Map properties) {
if (properties != null) {
for (final Iterator i = properties.keySet().iterator(); i.hasNext();) {
final Object keyo = i.next();
final Object val = properties.get(keyo);
final String key = (keyo instanceof String) ? (String) keyo
: keyo.toString();
input.setProperty(key, val);
}
}
return input;
}
public void sendMessage(ID target, ID thread, Type type, String subject,
String body, Map properties2) throws IOException {
if (target == null)
throw new IOException("XMPP target for message cannot be null");
if (body == null)
body = "";
final Message aMsg = new Message();
aMsg.setBody(body);
if (thread != null)
aMsg.setThread(thread.getName());
if (type != null)
aMsg.setType(type);
if (subject != null)
aMsg.setSubject(subject);
setPropertiesInPacket(aMsg, properties2);
sendMessage(target, aMsg);
}
public void sendPresenceUpdate(ID target, Presence presence)
throws IOException {
if (presence == null)
throw new IOException("presence cannot be null");
presence.setFrom(connection.getUser());
if (target != null)
presence.setTo(target.getName());
synchronized (this) {
if (!isConnected())
throw new IOException("not connected");
connection.sendPacket(presence);
}
}
public void sendRosterAdd(String user, String name, String[] groups)
throws IOException, XMPPException {
final Roster r = getRoster();
r.createEntry(user, name, groups);
}
public void sendRosterRemove(String user) throws XMPPException, IOException {
final Roster r = getRoster();
final RosterEntry re = r.getEntry(user);
r.removeEntry(re);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ecf.provider.xmpp.IIMMessageSender#getRoster()
*/
public Roster getRoster() throws IOException {
if (connection == null || !connection.isConnected())
return null;
return connection.getRoster();
}
}