/* * Copyright 2000-2006 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.communicator.jabber.impl; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import com.thoughtworks.xstream.XStream; import jetbrains.communicator.core.users.PresenceMode; import jetbrains.communicator.core.users.UserPresence; import jetbrains.communicator.ide.IDEFacade; import jetbrains.communicator.jabber.AccountInfo; import jetbrains.communicator.jabber.ConnectionListener; import jetbrains.communicator.jabber.JabberFacade; import jetbrains.communicator.jabber.VCardInfo; import jetbrains.communicator.util.StringUtil; import jetbrains.communicator.util.WaitFor; import jetbrains.communicator.util.XMLUtil; import org.apache.log4j.Logger; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jetbrains.annotations.NonNls; import org.jivesoftware.smack.*; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smackx.packet.VCard; import org.picocontainer.Disposable; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * @author Kir */ public class JabberFacadeImpl implements JabberFacade, Disposable { @NonNls private static final Logger LOG = Logger.getLogger(JabberFacadeImpl.class); public static final String FILE_NAME = "jabberSettings.xml"; private JabberSettings mySettings; private final IDEFacade myIdeFacade; private XStream myXStream; private final List<ConnectionListener> myConnectionListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private XMPPConnection myConnection; public JabberFacadeImpl(IDEFacade ideFacade) { myIdeFacade = ideFacade; } private void initSettingsIfNeeded() { if (mySettings == null) { myXStream = XMLUtil.createXStream(); mySettings = (JabberSettings) XMLUtil.fromXml(myXStream, myIdeFacade.getConfigDir(), FILE_NAME, false); if (mySettings == null) { mySettings = new JabberSettings(); } } } public void dispose() { disconnect(); } public void disconnect() { if (myConnection != null && myConnection.isConnected()) { myConnection.close(); } myConnection = null; } public String[] getServers() { SAXBuilder saxBuilder = new SAXBuilder(); try { Document document = saxBuilder.build(getClass().getResource("servers.xml")); Element rootElement = document.getRootElement(); List children = rootElement.getChildren("item", rootElement.getNamespace()); List<String> result = new ArrayList<>(); for (Object aChildren : children) { Element element = (Element) aChildren; result.add(element.getAttributeValue("jid")); } return ArrayUtil.toStringArray(result); } catch (JDOMException e) { LOG.error(e.getMessage(), e); } catch (IOException e) { LOG.error(e.getMessage(), e); } return new String[]{"jabber.org"}; } public AccountInfo getMyAccount() { initSettingsIfNeeded(); return mySettings.getAccount(); } public String connect() { if (isConnectedAndAuthenticated()) return null; AccountInfo info = getMyAccount(); if (info == null || !info.isLoginAllowed()) return null; return connect(info.getUsername(), info.getPassword(), info.getServer(), info.getPort(), info.isForceSSL()); } public String connect(String username, String password, String server, int port, boolean forceOldSSL) { return _createConnection(server, port, username, password, false, forceOldSSL); } public String createAccountAndConnect(String username, String password, String server, int port, boolean forceOldSSL) { return _createConnection(server, port, username, password, true, forceOldSSL); } private String _createConnection(String server, int port, final String username, String password, boolean createAccount, boolean forceOldSSL) { try { initSettingsIfNeeded(); XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() { public void connectionEstablished(XMPPConnection connection) { XMPPConnection.removeConnectionListener(this); fireConnected(connection); } }); String serviceName = server; if ("talk.google.com".equals(server)) { serviceName = "gmail.com"; } String user = username; int at = username.indexOf('@'); if (at > 0) { serviceName = username.substring(at + 1); user = username.substring(0, at); } XMPPConnection connection; if (forceOldSSL) { connection = new SSLXMPPConnection(server, port, serviceName); } else { connection = new XMPPConnection(server, port, serviceName); } if (createAccount && connection.getAccountManager().supportsAccountCreation()) { connection.getAccountManager().createAccount(user, password.replaceAll("&", "&")); } if (!connection.isConnected()) return StringUtil.getMsg("unable.to.connect.to", server, port); connection.login(user, password, IDETALK_RESOURCE); saveAccountData(server, port, username, password, forceOldSSL); if (rosterIsNotAvailable(connection)) { connection.close(); return StringUtil.getMsg("no.roster.try.again"); } myConnection = connection; fireAuthenticated(); myConnection.addConnectionListener(new SmackConnectionListener()); } catch (XMPPException e) { String message = getMessage(e); if (message != null && (message.indexOf("authentication failed") == -1 || password.length() != 0)) { LOG.info(message, e); } return message; } catch (IllegalStateException e) { LOG.info(e, e); return e.getMessage(); } return null; } private boolean rosterIsNotAvailable(final XMPPConnection connection) { new WaitFor(3000) { protected boolean condition() { return connection.getRoster() != null; } }; return connection.getRoster() == null; } private String getMessage(XMPPException e) { return e.getXMPPError() == null ? e.getMessage() : e.getXMPPError().toString(); } private void saveAccountData(String server, int port, String username, String password, boolean forceOldSSL) { getMyAccount().setServer(server); getMyAccount().setPort(port); getMyAccount().setUsername(username); getMyAccount().setPassword(password); getMyAccount().setForceSSL(forceOldSSL); } public void setVCardInfo(String nickName, String firstName, String lastName) throws XMPPException { assert isConnectedAndAuthenticated() : "Not connected or authenticated"; VCard vCard = new VCard(); vCard.setFirstName(firstName); vCard.setLastName(lastName); vCard.setNickName(nickName); PacketCollector collector = myConnection.createPacketCollector(new PacketIDFilter(vCard.getPacketID())); vCard.save(myConnection); IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); collector.cancel(); if (response == null) { throw new XMPPException("No response from the server."); } // If the server replied with an error, throw an exception. else if (response.getType() == IQ.Type.ERROR) { throw new XMPPException(response.getError()); } } public VCardInfo getVCard(String jabberId) { assert isConnectedAndAuthenticated() : "Not connected or authenticated"; VCard vCard = new VCard(); try { if (jabberId == null) { vCard.load(myConnection); } else { vCard.load(myConnection, jabberId); } } catch (XMPPException e) { return new VCardInfo("N/A", "N/A", "N/A"); } return new VCardInfo(vCard.getFirstName(), vCard.getLastName(), vCard.getNickName()); } public boolean isConnectedAndAuthenticated() { return myConnection != null && myConnection.isAuthenticated(); } public XMPPConnection getConnection() { return myConnection; } public void changeSubscription(String from, boolean subscribe) { LOG.info((subscribe ? "Accepted": "Denied" ) + " adding self to " + from + "'s contact list."); changeSubscription(from, subscribe ? Presence.Type.subscribed : Presence.Type.unsubscribed); } private void changeSubscription(String user, Presence.Type type) { Presence reply = new Presence(type); reply.setTo(user); myConnection.sendPacket(reply); } public void setOnlinePresence(UserPresence userPresence) { final Presence.Mode mode; String status = ""; PresenceMode presenceMode = userPresence.getPresenceMode(); switch(presenceMode) { case AWAY: mode = Presence.Mode.away; break; case EXTENDED_AWAY: mode = Presence.Mode.xa; break; case DND: mode = Presence.Mode.dnd; break; default: mode = Presence.Mode.available; } Presence presence = new Presence(Presence.Type.available, status, 0, mode); myConnection.sendPacket(presence); } public void saveSettings() { initSettingsIfNeeded(); if (!mySettings.getAccount().shouldRememberPassword()) { mySettings.getAccount().setPassword(""); } XMLUtil.toXml(myXStream, myIdeFacade.getConfigDir(), FILE_NAME, mySettings); } public void addUsers(String group, List<String> list) { if (!isConnectedAndAuthenticated()) return; String self = getConnection().getUser(); for (String id : list) { if (!self.startsWith(id)) { try { Roster roster = getConnection().getRoster(); RosterEntry oldEntry = roster.getEntry(id); if (oldEntry != null) { roster.removeEntry(oldEntry); } roster.createEntry(id, JabberTransport.getSimpleId(id), new String[]{group}); } catch (XMPPException e) { myIdeFacade.showMessage(StringUtil.getMsg("jabber.error.while.adding.user.title", id) ,StringUtil.getMsg("jabber.error.while.adding.user.text", id, getMessage(e)) ); LOG.info(getMessage(e), e); } } } } public void addConnectionListener(ConnectionListener connectionListener) { myConnectionListeners.add(connectionListener); } public void removeConnectionListener(ConnectionListener connectionListener) { myConnectionListeners.remove(connectionListener); } protected void fireConnected(XMPPConnection connection) { for (ConnectionListener listener : myConnectionListeners) { listener.connected(connection); } } protected void fireAuthenticated() { for (ConnectionListener listener : myConnectionListeners) { listener.authenticated(); } } protected void fireDisconnected(boolean onError) { for (ConnectionListener listener : myConnectionListeners) { listener.disconnected(onError); } } private class SmackConnectionListener implements org.jivesoftware.smack.ConnectionListener { public void connectionClosed() { myConnection.removeConnectionListener(this); fireDisconnected(false); } public void connectionClosedOnError(Exception exception) { try { myConnection.removeConnectionListener(this); } finally { fireDisconnected(true); } } public void reconectionSuccessful() { } public void reconnectingIn(int seconds) { } public void reconnectionFailed(Exception e) { try { myConnection.removeConnectionListener(this); } finally { fireDisconnected(true); } } } }