/******************************************************************************* * Copyright (c) 2004, 2007 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.provider.xmpp; import java.io.IOException; import java.net.ConnectException; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.ecf.core.ContainerConnectException; import org.eclipse.ecf.core.events.ContainerDisconnectedEvent; import org.eclipse.ecf.core.events.ContainerDisconnectingEvent; 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.security.Callback; import org.eclipse.ecf.core.security.CallbackHandler; import org.eclipse.ecf.core.security.IConnectContext; import org.eclipse.ecf.core.security.ObjectCallback; import org.eclipse.ecf.core.security.UnsupportedCallbackException; import org.eclipse.ecf.core.sharedobject.SharedObjectAddException; import org.eclipse.ecf.core.sharedobject.util.IQueueEnqueue; import org.eclipse.ecf.core.user.User; import org.eclipse.ecf.core.util.Event; import org.eclipse.ecf.filetransfer.ISendFileTransferContainerAdapter; import org.eclipse.ecf.internal.provider.xmpp.Messages; import org.eclipse.ecf.internal.provider.xmpp.XMPPChatRoomContainer; import org.eclipse.ecf.internal.provider.xmpp.XMPPChatRoomManager; import org.eclipse.ecf.internal.provider.xmpp.XMPPContainerAccountManager; import org.eclipse.ecf.internal.provider.xmpp.XMPPContainerContext; import org.eclipse.ecf.internal.provider.xmpp.XMPPContainerPresenceHelper; import org.eclipse.ecf.internal.provider.xmpp.XmppPlugin; import org.eclipse.ecf.internal.provider.xmpp.events.IQEvent; import org.eclipse.ecf.internal.provider.xmpp.events.MessageEvent; import org.eclipse.ecf.internal.provider.xmpp.events.PresenceEvent; import org.eclipse.ecf.internal.provider.xmpp.filetransfer.XMPPOutgoingFileTransferHelper; import org.eclipse.ecf.internal.provider.xmpp.search.XMPPUserSearchManager; import org.eclipse.ecf.internal.provider.xmpp.smack.ECFConnection; import org.eclipse.ecf.internal.provider.xmpp.smack.ECFConnectionObjectPacketEvent; import org.eclipse.ecf.internal.provider.xmpp.smack.ECFConnectionPacketEvent; import org.eclipse.ecf.presence.IAccountManager; import org.eclipse.ecf.presence.IPresenceContainerAdapter; import org.eclipse.ecf.presence.chatroom.IChatRoomContainer; import org.eclipse.ecf.presence.chatroom.IChatRoomManager; import org.eclipse.ecf.presence.im.IChatManager; import org.eclipse.ecf.presence.roster.IRosterManager; import org.eclipse.ecf.presence.search.IUserSearchManager; import org.eclipse.ecf.presence.service.IPresenceService; import org.eclipse.ecf.provider.comm.AsynchEvent; import org.eclipse.ecf.provider.comm.ConnectionCreateException; import org.eclipse.ecf.provider.comm.ISynchAsynchConnection; import org.eclipse.ecf.provider.generic.ClientSOContainer; import org.eclipse.ecf.provider.generic.ContainerMessage; import org.eclipse.ecf.provider.generic.SOConfig; import org.eclipse.ecf.provider.generic.SOContainerConfig; import org.eclipse.ecf.provider.generic.SOContext; import org.eclipse.ecf.provider.generic.SOWrapper; import org.eclipse.ecf.provider.xmpp.identity.XMPPID; import org.eclipse.osgi.util.NLS; import org.jivesoftware.smack.Roster; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.packet.MUCUser; import org.jivesoftware.smackx.packet.XHTMLExtension; /** * @since 3.0 */ public class XMPPContainer extends ClientSOContainer implements IPresenceService { public static final int DEFAULT_KEEPALIVE = 30000; public static final String CONNECT_NAMESPACE = XmppPlugin.getDefault() .getNamespaceIdentifier(); public static final String CONTAINER_HELPER_ID = XMPPContainer.class .getName() + ".xmpphandler"; //$NON-NLS-1$ protected static final String GOOGLE_SERVICENAME = "gmail.com"; //$NON-NLS-1$ private static final String[] googleHosts = { GOOGLE_SERVICENAME, "talk.google.com", "googlemail.com" }; //$NON-NLS-1$ //$NON-NLS-2$ public static final String XMPP_GOOGLE_OVERRIDE_PROP_NAME = "ecf.xmpp.google.override"; //$NON-NLS-1$ private static Set googleNames = new HashSet(); static { for (int i = 0; i < googleHosts.length; i++) googleNames.add(googleHosts[i]); final String override = System .getProperty(XMPP_GOOGLE_OVERRIDE_PROP_NAME); if (override != null) googleNames.add(override.toLowerCase()); } protected int keepAlive = 0; XMPPContainerAccountManager accountManager = null; XMPPChatRoomManager chatRoomManager = null; XMPPOutgoingFileTransferHelper outgoingFileTransferContainerAdapter = null; XMPPContainerPresenceHelper presenceHelper = null; /** * @since 3.0 */ XMPPUserSearchManager searchManager = null; protected ID presenceHelperID = null; protected XMPPContainer(SOContainerConfig config, int keepAlive) throws Exception { super(config); this.keepAlive = keepAlive; accountManager = new XMPPContainerAccountManager(); chatRoomManager = new XMPPChatRoomManager(getID()); searchManager = new XMPPUserSearchManager(); this.presenceHelperID = IDFactory.getDefault().createStringID( CONTAINER_HELPER_ID); presenceHelper = new XMPPContainerPresenceHelper(this); outgoingFileTransferContainerAdapter = new XMPPOutgoingFileTransferHelper( this); } public XMPPContainer() throws Exception { this(DEFAULT_KEEPALIVE); } public XMPPContainer(int ka) throws Exception { this(new SOContainerConfig(IDFactory.getDefault().createGUID()), ka); } public XMPPContainer(String userhost, int ka) throws Exception { this(new SOContainerConfig(IDFactory.getDefault().createStringID( userhost)), ka); } /** * @since 3.2 */ protected boolean verifySharedObjectMessageTarget(ID containerID) { return true; } /** * @since 3.2 */ protected void sendMessage(ContainerMessage data) throws IOException { synchronized (getConnectLock()) { ID connectedID = getConnectedID(); if (connectedID == null) throw new ConnectException("Container not connected"); //$NON-NLS-1$ synchronized (getGroupMembershipLock()) { if (!connectedID.equals(data.getToContainerID())) queueContainerMessage(data); } } } public IRosterManager getRosterManager() { return presenceHelper.getRosterManager(); } /** * @since 3.0 */ public IUserSearchManager getUserSearchManager() { return searchManager; } public IAccountManager getAccountManager() { return accountManager; } public IChatRoomManager getChatRoomManager() { return chatRoomManager; } public IChatManager getChatManager() { return presenceHelper.getChatManager(); } /* * (non-Javadoc) * * @see org.eclipse.ecf.provider.generic.SOContainer#getConnectNamespace() */ public Namespace getConnectNamespace() { return IDFactory.getDefault().getNamespaceByName( XmppPlugin.getDefault().getNamespaceIdentifier()); } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.ClientSOContainer#connect(org.eclipse * .ecf.core.identity.ID, org.eclipse.ecf.core.security.IConnectContext) */ public void connect(ID remote, IConnectContext joinContext) throws ContainerConnectException { try { getSharedObjectManager().addSharedObject(presenceHelperID, presenceHelper, null); super.connect(remote, joinContext); XmppPlugin.getDefault().registerService(this); } catch (final ContainerConnectException e) { disconnect(); throw e; } catch (final SharedObjectAddException e1) { disconnect(); throw new ContainerConnectException(NLS.bind( Messages.XMPPContainer_EXCEPTION_ADDING_SHARED_OBJECT, presenceHelperID), e1); } } /* * (non-Javadoc) * * @see org.eclipse.ecf.provider.generic.ClientSOContainer#disconnect() */ public void disconnect() { final ID groupID = getConnectedID(); fireContainerEvent(new ContainerDisconnectingEvent(this.getID(), groupID)); synchronized (getConnectLock()) { // If we are currently connected if (isConnected()) { XmppPlugin.getDefault().unregisterService(this); final ISynchAsynchConnection conn = getConnection(); synchronized (conn) { synchronized (getGroupMembershipLock()) { handleLeave(groupID, conn); } } } this.connection = null; remoteServerID = null; accountManager.setConnection(null); chatRoomManager.setConnection(null, null, null); outgoingFileTransferContainerAdapter.setConnection(null); presenceHelper.disconnect(); getSharedObjectManager().removeSharedObject(presenceHelperID); } // notify listeners fireContainerEvent(new ContainerDisconnectedEvent(this.getID(), groupID)); } /* * (non-Javadoc) * * @see org.eclipse.ecf.provider.generic.ClientSOContainer#dispose() */ public void dispose() { chatRoomManager.dispose(); accountManager.dispose(); outgoingFileTransferContainerAdapter.dispose(); super.dispose(); } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.SOContainer#getAdapter(java.lang.Class) */ public Object getAdapter(Class clazz) { if (clazz.equals(IPresenceContainerAdapter.class)) return this; if (clazz.equals(ISendFileTransferContainerAdapter.class)) return outgoingFileTransferContainerAdapter; else return super.getAdapter(clazz); } private String trimResourceFromJid(String jid) { int slashIndex = jid.indexOf('/'); if (slashIndex > 0) { return jid.substring(slashIndex + 1); } else return null; } private void resetTargetResource(ID originalTarget, Object serverData) { // Reset resource to that given by server if (originalTarget instanceof XMPPID) { XMPPID xmppOriginalTarget = (XMPPID) originalTarget; if (serverData != null && serverData instanceof String) { String jid = (String) serverData; String jidResource = trimResourceFromJid(jid); if (jidResource != null) { xmppOriginalTarget.setResourceName(jidResource); } } } } protected ID handleConnectResponse(ID originalTarget, Object serverData) throws Exception { if (originalTarget != null && !originalTarget.equals(getID())) { // First reset target resource to whatever the server says it is resetTargetResource(originalTarget, serverData); addNewRemoteMember(originalTarget, null); final ECFConnection conn = getECFConnection(); accountManager.setConnection(conn.getXMPPConnection()); chatRoomManager.setConnection(getConnectNamespace(), originalTarget, conn); searchManager.setConnection(getConnectNamespace(), originalTarget, conn); searchManager.setEnabled(!isGoogle(originalTarget)); presenceHelper.setUser(new User(originalTarget)); outgoingFileTransferContainerAdapter.setConnection(conn .getXMPPConnection()); return originalTarget; } else throw new ConnectException( Messages.XMPPContainer_EXCEPTION_INVALID_RESPONSE_FROM_SERVER); } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.ClientSOContainer#createConnection(org * .eclipse.ecf.core.identity.ID, java.lang.Object) */ protected ISynchAsynchConnection createConnection(ID remoteSpace, Object data) throws ConnectionCreateException { final boolean google = isGoogle(remoteSpace); return new ECFConnection(google, getConnectNamespace(), receiver); } protected boolean isGoogle(ID remoteSpace) { if (remoteSpace instanceof XMPPID) { final XMPPID theID = (XMPPID) remoteSpace; final String host = theID.getHostname(); if (host == null) return false; return googleNames.contains(host.toLowerCase()); } return false; } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.ClientSOContainer#getConnectData(org * .eclipse.ecf.core.identity.ID, * org.eclipse.ecf.core.security.IConnectContext) */ protected Object getConnectData(ID remote, IConnectContext joinContext) throws IOException, UnsupportedCallbackException { final Callback[] callbacks = createAuthorizationCallbacks(); if (joinContext != null && callbacks != null && callbacks.length > 0) { final CallbackHandler handler = joinContext.getCallbackHandler(); if (handler != null) { handler.handle(callbacks); } if (callbacks[0] instanceof ObjectCallback) { final ObjectCallback cb = (ObjectCallback) callbacks[0]; return cb.getObject(); } } return null; } protected Object createConnectData(ID target, Callback[] cbs, Object data) { // first one is password callback if (cbs.length > 0) { if (cbs[0] instanceof ObjectCallback) { final ObjectCallback cb = (ObjectCallback) cbs[0]; return cb.getObject(); } } return data; } protected Callback[] createAuthorizationCallbacks() { final Callback[] cbs = new Callback[1]; cbs[0] = new ObjectCallback(); return cbs; } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.ClientSOContainer#getConnectTimeout() */ protected int getConnectTimeout() { return keepAlive; } protected Roster getRoster() throws IOException { final ECFConnection connection = getECFConnection(); if (connection != null) { return connection.getRoster(); } else return null; } protected void deliverEvent(Event evt) { final SOWrapper wrap = getSharedObjectWrapper(presenceHelperID); if (wrap != null) wrap.deliverEvent(evt); } protected void handleXMPPMessage(Packet aPacket) throws IOException { if (!handleAsExtension(aPacket)) { if (aPacket instanceof IQ) { deliverEvent(new IQEvent((IQ) aPacket)); } else if (aPacket instanceof Message) { deliverEvent(new MessageEvent((Message) aPacket)); } else if (aPacket instanceof Presence) { deliverEvent(new PresenceEvent((Presence) aPacket)); } else { log(NLS.bind(Messages.XMPPContainer_UNEXPECTED_XMPP_MESSAGE, aPacket.toXML()), null); } } } protected boolean handleAsExtension(Packet packet) { final Iterator i = packet.getExtensions().iterator(); for (; i.hasNext();) { final Object extension = i.next(); if (extension instanceof XHTMLExtension) { final XHTMLExtension xhtmlExtension = (XHTMLExtension) extension; deliverEvent(new MessageEvent((Message) packet, xhtmlExtension.getBodies())); return true; } if (packet instanceof Presence && extension instanceof MUCUser) { return true; } } return false; } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.SOContainer#createSharedObjectContext * (org.eclipse.ecf.provider.generic.SOConfig, * org.eclipse.ecf.core.sharedobject.util.IQueueEnqueue) */ protected SOContext createSharedObjectContext(SOConfig soconfig, IQueueEnqueue queue) { return new XMPPContainerContext(soconfig.getSharedObjectID(), soconfig.getHomeContainerID(), this, soconfig.getProperties(), queue); } /* * (non-Javadoc) * * @see * org.eclipse.ecf.provider.generic.ClientSOContainer#processAsynch(org. * eclipse.ecf.provider.comm.AsynchEvent) */ protected void processAsynch(AsynchEvent e) { try { if (e instanceof ECFConnectionPacketEvent) { // It's a regular xmpp message handleXMPPMessage((Packet) e.getData()); return; } else if (e instanceof ECFConnectionObjectPacketEvent) { // It's an ECF object message final ECFConnectionObjectPacketEvent evt = (ECFConnectionObjectPacketEvent) e; final Object obj = evt.getObjectValue(); // this should be a ContainerMessage final Object cm = deserializeContainerMessage((byte[]) obj); if (cm == null) throw new IOException( Messages.XMPPContainer_EXCEPTION_DESERIALIZED_OBJECT_NULL); final ContainerMessage contMessage = (ContainerMessage) cm; final IChatRoomContainer chat = chatRoomManager .findReceiverChatRoom(contMessage.getToContainerID()); if (chat != null && chat instanceof XMPPChatRoomContainer) { final XMPPChatRoomContainer cont = (XMPPChatRoomContainer) chat; cont.handleContainerMessage(contMessage); return; } final Object data = contMessage.getData(); if (data instanceof ContainerMessage.CreateMessage) { handleCreateMessage(contMessage); } else if (data instanceof ContainerMessage.CreateResponseMessage) { handleCreateResponseMessage(contMessage); } else if (data instanceof ContainerMessage.SharedObjectMessage) { handleSharedObjectMessage(contMessage); } else if (data instanceof ContainerMessage.SharedObjectDisposeMessage) { handleSharedObjectDisposeMessage(contMessage); } else { debug(NLS .bind(Messages.XMPPContainer_UNRECOGONIZED_CONTAINER_MESSAGE, contMessage)); } } else { // Unexpected type... log(NLS.bind(Messages.XMPPContainer_UNEXPECTED_EVENT, e), null); } } catch (final Exception except) { log(NLS.bind(Messages.XMPPContainer_EXCEPTION_HANDLING_ASYCH_EVENT, e), except); } } public ECFConnection getECFConnection() { return (ECFConnection) super.getConnection(); } public XMPPConnection getXMPPConnection() { final ECFConnection conn = getECFConnection(); if (conn == null) return null; else return conn.getXMPPConnection(); } // utility methods protected void log(String msg, Throwable e) { XmppPlugin.log(msg, e); } }