From b4be51c2f7fd9c5411c58456e8bb68e56536ec2f Mon Sep 17 00:00:00 2001 From: Damian Minkov Date: Thu, 26 Mar 2009 15:45:50 +0000 Subject: Fix issue #613. Update jabber to newest version. Fix some errors with lockups when opening multichats from url argument (and saved chatrooms) and at same time receiving messages on that chat room. --- .../impl/gui/main/chat/ChatWindowManager.java | 18 +- .../chat/conference/ChatRoomProviderWrapper.java | 5 +- .../chat/conference/ConferenceChatManager.java | 2 +- .../impl/gui/main/chatroomslist/ChatRoomList.java | 9 + .../impl/protocol/jabber/ChatRoomJabberImpl.java | 12 +- .../impl/protocol/jabber/InfoRetreiver.java | 8 - .../impl/protocol/jabber/JabberActivator.java | 38 ++ .../OperationSetMultiUserChatJabberImpl.java | 131 ++-- .../jabber/ProtocolProviderServiceJabberImpl.java | 31 +- .../jabber/ServerStoredContactListJabberImpl.java | 3 +- .../impl/protocol/jabber/UriHandlerJabberImpl.java | 689 +++++++++++++++++++++ .../protocol/jabber/jabber.provider.manifest.mf | 2 + 12 files changed, 845 insertions(+), 103 deletions(-) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java (limited to 'src/net/java') diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindowManager.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindowManager.java index 48b5329..c53d890 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindowManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindowManager.java @@ -39,8 +39,20 @@ public class ChatWindowManager * @param setSelected specifies whether we should bring the chat to front * after creating it. */ - public void openChat(ChatPanel chatPanel, boolean setSelected) + public void openChat(final ChatPanel chatPanel, final boolean setSelected) { + if(!SwingUtilities.isEventDispatchThread()) + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + openChat(chatPanel, setSelected); + } + }); + return; + } + synchronized (syncChat) { ChatWindow chatWindow = chatPanel.getChatWindow(); @@ -154,7 +166,7 @@ public class ChatWindowManager ChatRoomWrapper chatRoomWrapper = (ChatRoomWrapper) descriptor; - if(chatRoomWrapper.getChatRoom().equals(chatRoom) + if(chatRoomWrapper.getChatRoomID().equals(chatRoom.getIdentifier()) && getChat(chatSession).isShown()) { return true; @@ -409,8 +421,8 @@ public class ChatWindowManager } else return createChat(chatRoomWrapper); + } } - } /** * Returns the chat panel corresponding to the given chat room. diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomProviderWrapper.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomProviderWrapper.java index 8c9250d..cb59a13 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomProviderWrapper.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ChatRoomProviderWrapper.java @@ -152,10 +152,11 @@ public class ChatRoomProviderWrapper */ public ChatRoomWrapper findChatRoomWrapperForChatRoom(ChatRoom chatRoom) { + // compare ids, cause saved chatrooms don't have ChatRoom object + // but Id's are the same for (ChatRoomWrapper chatRoomWrapper : chatRoomsOrderedCopy) { - if (chatRoomWrapper.getChatRoom() != null - && chatRoomWrapper.getChatRoom().equals(chatRoom)) + if (chatRoomWrapper.getChatRoomID().equals(chatRoom.getIdentifier())) { return chatRoomWrapper; } diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java index 2d9a50e..f211957 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/conference/ConferenceChatManager.java @@ -691,7 +691,7 @@ public class ConferenceChatManager GuiActivator.getUIService().getMainFrame(), GuiActivator.getResources().getI18NString("service.gui.WARNING"), GuiActivator.getResources().getI18NString( - "service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED=")) + "service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED")) .showDialog(); return; diff --git a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomList.java b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomList.java index 9269ac1..d4cfb7d 100644 --- a/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomList.java +++ b/src/net/java/sip/communicator/impl/gui/main/chatroomslist/ChatRoomList.java @@ -246,7 +246,16 @@ public class ChatRoomList = provider.findChatRoomWrapperForChatRoom(chatRoom); if (chatRoomWrapper != null) + { + // stored chatrooms has no chatroom, but their + // id is the same as the chatroom we are searching wrapper for + if(chatRoomWrapper.getChatRoom() == null) + { + chatRoomWrapper.setChatRoom(chatRoom); + } + return chatRoomWrapper; + } } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java index f5cecd9..b47b38a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ChatRoomJabberImpl.java @@ -20,6 +20,7 @@ import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.util.*; import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.muc.*; +import org.jivesoftware.smackx.packet.DiscoverInfo; /** * Implements chat rooms for jabber. The class encapsulates instances of the @@ -1724,10 +1725,15 @@ public class ChatRoomJabberImpl String roomName = multiUserChat.getRoom(); try { - RoomInfo info = MultiUserChat.getRoomInfo( - provider.getConnection(), roomName); + // Do not use getRoomInfo, as it has bug and + // throws NPE + DiscoverInfo info = + ServiceDiscoveryManager.getInstanceFor(provider.getConnection()). + discoverInfo(roomName); + if (info != null) - persistent = info.isPersistent(); + persistent = info.containsFeature("muc_persistent"); + } catch (Exception ex) { logger.warn("could not get persistent state for room :" + diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java index 955d4a3..153978d 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/InfoRetreiver.java @@ -257,14 +257,6 @@ public class InfoRetreiver + this + " : " + exc.getMessage() , exc); } - - - Iterator i = result.iterator(); - while (i.hasNext()) - { - Object object = i.next(); - logger.info("--------------- " + object.getClass() + " " + object); - } } retreivedDetails.put(contactAddress, result); diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java index 3b1f100..a66bfb7 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/JabberActivator.java @@ -10,6 +10,7 @@ import java.util.*; import org.osgi.framework.*; import net.java.sip.communicator.service.configuration.*; +import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.media.*; @@ -48,6 +49,10 @@ public class JabberActivator */ private static ProtocolProviderFactoryJabberImpl jabberProviderFactory = null; + private UriHandlerJabberImpl uriHandlerImpl = null; + + private static UIService uiService = null; + /** * Called when this bundle is started so the Framework can perform the * bundle-specific activities necessary to start this bundle. @@ -67,6 +72,13 @@ public class JabberActivator jabberProviderFactory = new ProtocolProviderFactoryJabberImpl(); + /* + * Install the UriHandler prior to registering the factory service in + * order to allow it to detect when the stored accounts are loaded + * (because they may be asynchronously loaded). + */ + uriHandlerImpl = new UriHandlerJabberImpl(jabberProviderFactory); + //reg the jabber account man. jabberPpFactoryServReg = context.registerService( ProtocolProviderFactory.class.getName(), @@ -153,5 +165,31 @@ public class JabberActivator { jabberProviderFactory.stop(); jabberPpFactoryServReg.unregister(); + + if (uriHandlerImpl != null) + { + uriHandlerImpl.dispose(); + uriHandlerImpl = null; + } + } + + /** + * Returns a reference to the UIService implementation currently registered + * in the bundle context or null if no such implementation was found. + * + * @return a reference to a UIService implementation currently registered + * in the bundle context or null if no such implementation was found. + */ + public static UIService getUIService() + { + if(uiService == null) + { + ServiceReference uiServiceReference + = bundleContext.getServiceReference( + UIService.class.getName()); + uiService = (UIService)bundleContext + .getService(uiServiceReference); + } + return uiService; } } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java index 0286875..6af4b1d 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/OperationSetMultiUserChatJabberImpl.java @@ -13,7 +13,6 @@ import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; -import org.jivesoftware.smack.packet.*; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smackx.*; import org.jivesoftware.smackx.muc.*; @@ -114,7 +113,7 @@ public class OperationSetMultiUserChatJabberImpl invitationListeners.remove(listener); } } - + /** * Adds a listener that will be notified of changes in our status in a chat * room such as us being kicked, banned or dropped. @@ -236,7 +235,7 @@ public class OperationSetMultiUserChatJabberImpl private ChatRoom createLocalChatRoomInstance(MultiUserChat muc) { synchronized(chatRoomCache) - { + { ChatRoomJabberImpl chatRoom = new ChatRoomJabberImpl(muc, jabberProvider); cacheChatRoom(chatRoom); @@ -246,7 +245,7 @@ public class OperationSetMultiUserChatJabberImpl // ChatRoomInvitationRejectionListener. muc.addInvitationRejectionListener( new SmackInvitationRejectionListener(chatRoom)); - + return chatRoom; } } @@ -264,7 +263,7 @@ public class OperationSetMultiUserChatJabberImpl * @throws OperationNotSupportedException if the server does not support * multi user chat */ - public ChatRoom findRoom(String roomName) + public synchronized ChatRoom findRoom(String roomName) throws OperationFailedException, OperationNotSupportedException { //make sure we are connected and multichat is supported. @@ -279,59 +278,23 @@ public class OperationSetMultiUserChatJabberImpl try { - RoomInfo infos = MultiUserChat.getRoomInfo( - getXmppConnection(), canonicalRoomName); - if (infos.getRoom().equals(canonicalRoomName)) - { - MultiUserChat muc = + // throws Exception if room does not exist + // do not use MultiUserChat.getRoomInfo as there is a bug which + // throws NPE + ServiceDiscoveryManager.getInstanceFor(getXmppConnection()). + discoverInfo(canonicalRoomName); + + MultiUserChat muc = new MultiUserChat(getXmppConnection(), canonicalRoomName); - room = new ChatRoomJabberImpl(muc, jabberProvider); - chatRoomCache.put(canonicalRoomName, room); - return room; - } - } - catch (XMPPException xe) - { - return null; - } - catch (NullPointerException ne) - { - // caused by some bug in smack, we will try another method - } + room = new ChatRoomJabberImpl(muc, jabberProvider); + chatRoomCache.put(canonicalRoomName, room); - try - { - // getHostedRooms will let us if the room doesnt exists - // by raising an XMPPException with - // XMPPError.Condition.item_not_found as error condition. - // if we get anything else, we can conclude so we create - // the MultiUserChat instance and the failure point will be - // join method - Collection co = - MultiUserChat.getHostedRooms( - getXmppConnection(), canonicalRoomName); - } - catch (XMPPException xe) + return room; + } catch (XMPPException e) { - if (xe.getXMPPError().getCondition().equals( - XMPPError.Condition.item_not_found.toString())) - { - return null; - } - else - { - MultiUserChat muc = - new MultiUserChat( - getXmppConnection(), canonicalRoomName); - - room = new ChatRoomJabberImpl(muc, - jabberProvider); - - chatRoomCache.put(canonicalRoomName, room); - return room; - } + // room not found + return null; } - return null; } /** @@ -342,7 +305,7 @@ public class OperationSetMultiUserChatJabberImpl * a given connection. */ public List getCurrentlyJoinedChatRooms() - { + { synchronized(chatRoomCache) { List joinedRooms @@ -415,9 +378,9 @@ public class OperationSetMultiUserChatJabberImpl OperationNotSupportedException { assertSupportedAndConnected(); - + List list = new LinkedList(); - + //first retrieve all conference service names available on this server Iterator serviceNames = null; try @@ -484,7 +447,7 @@ public class OperationSetMultiUserChatJabberImpl if(contact.getProtocolProvider() .getOperationSet(OperationSetMultiUserChat.class) != null) return true; - + return false; } @@ -650,11 +613,11 @@ public class OperationSetMultiUserChatJabberImpl return (List) joinedRoomsIter; } - + /** * Delivers a LocalUserChatRoomPresenceChangeEvent to all * registered LocalUserChatRoomPresenceListeners. - * + * * @param chatRoom the ChatRoom which has been joined, left, etc. * @param eventType the type of this event; one of LOCAL_USER_JOINED, * LOCAL_USER_LEFT, etc. @@ -668,7 +631,7 @@ public class OperationSetMultiUserChatJabberImpl chatRoom, eventType, reason); - + Iterator listeners = null; synchronized (presenceListeners) { @@ -679,7 +642,7 @@ public class OperationSetMultiUserChatJabberImpl { LocalUserChatRoomPresenceListener listener = (LocalUserChatRoomPresenceListener) listeners.next(); - + listener.localUserPresenceChanged(evt); } } @@ -687,11 +650,11 @@ public class OperationSetMultiUserChatJabberImpl /** * Delivers a ChatRoomInvitationReceivedEvent to all * registered ChatRoomInvitationListeners. - * + * * @param targetChatRoom the room that invitation refers to * @param inviter the inviter that sent the invitation * @param reason the reason why the inviter sent the invitation - * @param password the password to use when joining the room + * @param password the password to use when joining the room */ public void fireInvitationEvent( ChatRoom targetChatRoom, @@ -704,11 +667,11 @@ public class OperationSetMultiUserChatJabberImpl inviter, reason, password); - + ChatRoomInvitationReceivedEvent evt = new ChatRoomInvitationReceivedEvent(this, invitation, new Date(System.currentTimeMillis())); - + Iterator listeners = null; synchronized (invitationListeners) { @@ -723,11 +686,11 @@ public class OperationSetMultiUserChatJabberImpl listener.invitationReceived(evt); } } - + /** * Delivers a ChatRoomInvitationRejectedEvent to all * registered ChatRoomInvitationRejectionListeners. - * + * * @param sourceChatRoom the room that invitation refers to * @param invitee the name of the invitee that rejected the invitation * @param reason the reason of the rejection @@ -740,18 +703,18 @@ public class OperationSetMultiUserChatJabberImpl = new ChatRoomInvitationRejectedEvent( this, sourceChatRoom, invitee, reason, new Date(System.currentTimeMillis())); - + Iterator listeners = null; synchronized (invitationRejectionListeners) { listeners = new ArrayList(invitationRejectionListeners).iterator(); } - + while (listeners.hasNext()) { ChatRoomInvitationRejectionListener listener = (ChatRoomInvitationRejectionListener) listeners.next(); - + listener.invitationRejected(evt); } } @@ -765,11 +728,11 @@ public class OperationSetMultiUserChatJabberImpl { /** * Called when the an invitation to join a MUC room is received.

- * + * * If the room is password-protected, the invitee will receive a * password to use to join the room. If the room is members-only, the * the invitee may be added to the member list. - * + * * @param conn the XMPPConnection that received the invitation. * @param room the room that invitation refers to. * @param inviter the inviter that sent the invitation. @@ -808,7 +771,7 @@ public class OperationSetMultiUserChatJabberImpl } } } - + /** * A listener that is fired anytime an invitee declines or rejects an * invitation. @@ -817,39 +780,39 @@ public class OperationSetMultiUserChatJabberImpl implements InvitationRejectionListener { private ChatRoom chatRoom; - + /** * Creates an instance of SmackInvitationRejectionListener and * passes to it the chat room for which it will listen for rejection * events. - * + * * @param chatRoom */ public SmackInvitationRejectionListener(ChatRoom chatRoom) { this.chatRoom = chatRoom; } - + /** * Called when the invitee declines the invitation. - * + * * @param invitee the invitee that declined the invitation. * (e.g. hecate@shakespeare.lit). * @param reason the reason why the invitee declined the invitation. */ public void invitationDeclined(String invitee, String reason) - { + { fireInvitationRejectedEvent(chatRoom, invitee, reason); } } - + /** * Our listener that will tell us when we're registered to jabber and the * smack MultiUserChat is ready to accept us as a listener. */ private class RegistrationStateListener implements RegistrationStateChangeListener - { + { /** * The method is called by a ProtocolProvider implementation whenver * a change in the registration state of the corresponding provider had @@ -862,14 +825,14 @@ public class OperationSetMultiUserChatJabberImpl if (evt.getNewState() == RegistrationState.REGISTERED) { logger.debug("adding an Invitation listener to the smack muc"); - + MultiUserChat.addInvitationListener( jabberProvider.getConnection(), new SmackInvitationListener()); } } } - + /** * Updates corresponding chat room members when a contact has been modified * in our contact list. @@ -924,7 +887,7 @@ public class OperationSetMultiUserChatJabberImpl /** * Finds all chat room members, which name corresponds to the name of the * given contact and updates their contact references. - * + * * @param contact the contact we're looking correspondences for. */ private void updateChatRoomMembers(Contact contact) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java index 3e24ffd..fcffd6a 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ProtocolProviderServiceJabberImpl.java @@ -358,7 +358,36 @@ public class ProtocolProviderServiceJabberImpl if(accountResource == null || accountResource.equals("")) accountResource = "sip-comm"; - connection.login(userID, password, accountResource); + SASLAuthentication.supportSASLMechanism("PLAIN", 0); + + try + { + connection.login(userID, password, accountResource); + } catch (XMPPException e1) + { + // after updating to new smack lib + // login mechanisum changed + // this is a way to avoid the problem + try + { + // server disconnect us after such un error + // cleanup + try + { + connection.disconnect(); + } catch (Exception e) + {} + // and connect again + connection.connect(); + // logging in to google need and service name + connection.login(userID + "@" + serviceName, + password, accountResource); + } catch (XMPPException e2) + { + // if it happens once again throw the original exception + throw e1; + } + } if(connection.isAuthenticated()) { diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java index e03214a..356ab0b 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ServerStoredContactListJabberImpl.java @@ -543,7 +543,8 @@ public class ServerStoredContactListJabberImpl while (iter.hasNext()) { ContactJabberImpl item = (ContactJabberImpl) iter.next(); - roster.removeEntry(item.getSourceEntry()); + if(item.isPersistent()) + roster.removeEntry(item.getSourceEntry()); } } catch (XMPPException ex) diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java b/src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java new file mode 100644 index 0000000..67ab291 --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java @@ -0,0 +1,689 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.protocol.jabber; + +import java.util.*; +import java.util.regex.*; + +import org.osgi.framework.*; + +import net.java.sip.communicator.service.argdelegation.*; +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + + +/** + * The jabber implementation of the URI handler. This class handles xmpp URIs by + * trying to establish a chat with them or add you to a chatroom. + * + * @author Emil Ivov + * @author Damian Minkov + */ +public class UriHandlerJabberImpl + implements UriHandler, ServiceListener, AccountManagerListener +{ + private static final Logger logger = + Logger.getLogger(UriHandlerJabberImpl.class); + + /** + * The protocol provider factory that created us. + */ + private final ProtocolProviderFactory protoFactory; + + /** + * A reference to the OSGi registration we create with this handler. + */ + private ServiceRegistration ourServiceRegistration = null; + + /** + * The object that we are using to synchronize our service registration. + */ + private final Object registrationLock = new Object(); + + /** + * The AccountManager which loads the stored accounts of + * {@link #protoFactory} and to be monitored when the mentioned loading is + * complete so that any pending {@link #uris} can be handled + */ + private AccountManager accountManager; + + /** + * The indicator (and its synchronization lock) which determines whether the + * stored accounts of {@link #protoFactory} have already been loaded. + *

+ * Before the loading of the stored accounts (even if there're none) of the + * protoFactory is complete, no handling of URIs is to be + * performed because there's neither information which account to handle the + * URI in case there're stored accounts available nor ground for warning the + * user a registered account is necessary to handle URIs at all in case + * there're no stored accounts. + *

+ */ + private final boolean[] storedAccountsAreLoaded = new boolean[1]; + + /** + * The list of URIs which have received requests for handling before the + * stored accounts of the {@link #protoFactory} have been loaded. They will + * be handled as soon as the mentioned loading completes. + */ + private List uris; + + /** + * Marks network fails in order to avoid endless loops. + */ + private boolean networkFailReceived = false; + + /** + * Creates an instance of this uri handler, so that it would start handling + * URIs by passing them to the providers registered by protoFactory + * . + * + * @param parentProvider the provider that created us. + * + * @throws NullPointerException if protoFactory is null. + */ + public UriHandlerJabberImpl(ProtocolProviderFactory protoFactory) + throws NullPointerException + { + if (protoFactory == null) + { + throw new NullPointerException( + "The ProtocolProviderFactory that a UriHandler is created with " + + " cannot be null."); + } + + this.protoFactory = protoFactory; + + hookStoredAccounts(); + + this.protoFactory.getBundleContext().addServiceListener(this); + /* + * Registering the UriHandler isn't strictly necessary if the + * requirement to register the protoFactory after creating this instance + * is met. + */ + registerHandlerService(); + } + + /** + * Disposes of this UriHandler by, for example, removing the + * listeners it has added in its constructor (in order to prevent memory + * leaks, for one). + */ + public void dispose() + { + protoFactory.getBundleContext().removeServiceListener(this); + unregisterHandlerService(); + + unhookStoredAccounts(); + } + + /** + * Sets up (if not set up already) listening for the loading of the stored + * accounts of {@link #protoFactory} in order to make it possible to + * discover when the prerequisites for handling URIs are met. + */ + private void hookStoredAccounts() + { + if (accountManager == null) + { + BundleContext bundleContext = protoFactory.getBundleContext(); + + accountManager = + (AccountManager) bundleContext.getService(bundleContext + .getServiceReference(AccountManager.class.getName())); + accountManager.addListener(this); + } + } + + /** + * Reverts (if not reverted already) the setup performed by a previous chat + * to {@link #hookStoredAccounts()}. + */ + private void unhookStoredAccounts() + { + if (accountManager != null) + { + accountManager.removeListener(this); + accountManager = null; + } + } + + /* + * (non-Javadoc) + * + * @see + * net.java.sip.communicator.service.protocol.event.AccountManagerListener + * #handleAccountManagerEvent + * (net.java.sip.communicator.service.protocol.event.AccountManagerEvent) + */ + public void handleAccountManagerEvent(AccountManagerEvent event) + { + + /* + * When the loading of the stored accounts of protoFactory is complete, + * the prerequisites for handling URIs have been met so it's time to + * load any handling requests which have come before the loading and + * were thus delayed in uris. + */ + if ((AccountManagerEvent.STORED_ACCOUNTS_LOADED == event.getType()) + && (protoFactory == event.getFactory())) + { + List uris = null; + + synchronized (storedAccountsAreLoaded) + { + storedAccountsAreLoaded[0] = true; + + if (this.uris != null) + { + uris = this.uris; + this.uris = null; + } + } + + unhookStoredAccounts(); + + if (uris != null) + { + for (Iterator uriIter = uris.iterator(); uriIter + .hasNext();) + { + handleUri(uriIter.next()); + } + } + } + } + + /** + * Registers this UriHandler with the bundle context so that it could start + * handling URIs + */ + public void registerHandlerService() + { + synchronized (registrationLock) + { + if (ourServiceRegistration != null) + { + // ... we are already registered (this is probably + // happening during startup) + return; + } + + Hashtable registrationProperties = + new Hashtable(); + + registrationProperties.put(UriHandler.PROTOCOL_PROPERTY, + getProtocol()); + + ourServiceRegistration = + JabberActivator.bundleContext.registerService(UriHandler.class + .getName(), this, registrationProperties); + } + + } + + /** + * Unregisters this UriHandler from the bundle context. + */ + public void unregisterHandlerService() + { + synchronized (registrationLock) + { + if (ourServiceRegistration != null) + { + ourServiceRegistration.unregister(); + ourServiceRegistration = null; + } + } + } + + /** + * Returns the protocol that this handler is responsible for or "xmpp" in + * other words. + * + * @return the "xmpp" string to indicate that this handler is responsible for + * handling "xmpp" uris. + */ + public String getProtocol() + { + return "xmpp"; + } + + /** + * Parses the specified URI and creates a chat with the currently active + * im operation set. + * + * @param uri the xmpp URI that we have to handle. + */ + public void handleUri(String uri) + { + /* + * TODO If the requirement to register the factory service after + * creating this instance is broken, we'll end up not handling the URIs. + */ + synchronized (storedAccountsAreLoaded) + { + if (!storedAccountsAreLoaded[0]) + { + if (uris == null) + { + uris = new LinkedList(); + } + uris.add(uri); + return; + } + } + + ProtocolProviderService provider; + try + { + provider = selectHandlingProvider(uri); + } + catch (OperationFailedException exc) + { + // The operation has been canceled by the user. Bail out. + logger.trace("User canceled handling of uri " + uri); + return; + } + + // if provider is null then we need to tell the user to create an + // account + if (provider == null) + { + showErrorMessage( + "You need to configure at least one XMPP account \n" + + "to be able to call " + uri, null); + return; + } + + if(!uri.contains("?")) + { + OperationSetPersistentPresence presenceOpSet = + (OperationSetPersistentPresence) provider + .getOperationSet(OperationSetPersistentPresence.class); + + String contactId = uri.replaceFirst(getProtocol() + ":", ""); + + //todo check url!! + //Set the email pattern string + Pattern p = Pattern.compile(".+@.+\\.[a-z]+"); + if(!p.matcher(contactId).matches()) + { + showErrorMessage( + "Wrong contact id : " + uri, null); + return; + } + + Contact contact = presenceOpSet.findContactByID(contactId); + if(contact == null) + { + Object result = + JabberActivator.getUIService().getPopupDialog(). + showConfirmPopupDialog( + "Do you want to add the contact : " + contactId + " ?", + "Add contact", + PopupDialog.YES_NO_OPTION); + + if(result.equals(PopupDialog.YES_OPTION)) + { + ExportedWindow ex = JabberActivator.getUIService(). + getExportedWindow(ExportedWindow.ADD_CONTACT_WINDOW, + new String[]{contactId}); + ex.setVisible(true); + } + + return; + } + + JabberActivator.getUIService(). + getChat(contact).setChatVisible(true); + } + else + { + String croom = uri.replaceFirst(getProtocol() + ":", ""); + int ix = croom.indexOf("?"); + String param = croom.substring(ix + 1, croom.length()); + croom = croom.substring(0, ix); + + if(param.equalsIgnoreCase("join")) + { + OperationSetMultiUserChat mchatOpSet = + (OperationSetMultiUserChat) provider + .getOperationSet(OperationSetMultiUserChat.class); + + try + { + ChatRoom room = mchatOpSet.findRoom(croom); + + if(room != null) + { + room.join(); + } + } + catch (OperationFailedException exc) + { + // if we are not online we get this error + // will wait for it and then will try to handle once again + if(exc.getErrorCode() == OperationFailedException.NETWORK_FAILURE + && !networkFailReceived) + { + networkFailReceived = true; + OperationSetPresence presenceOpSet = + (OperationSetPresence) provider + .getOperationSet(OperationSetPresence.class); + presenceOpSet.addProviderPresenceStatusListener( + new ProviderStatusListener(uri, presenceOpSet)); + } + else + showErrorMessage("Error joining to " + croom, exc); + } + catch (OperationNotSupportedException exc) + { + showErrorMessage("Join to " + croom + ", not supported!", exc); + } + } + else + showErrorMessage( + "Unknown param : " + param, null); + } + } + + /** + * Informs the user that they need to be registered before chatting and + * asks them whether they would like us to do it for them. + * + * @param uri the uri that the user would like us to chat with after registering. + * @param provider the provider that we may have to reregister. + */ + private void promptForRegistration(String uri, + ProtocolProviderService provider) + { + int answer = + JabberActivator + .getUIService() + .getPopupDialog() + .showConfirmPopupDialog( + "You need to be online in order to chat and your " + + "account is currently offline. Do want to connect now?", + "Account is currently offline", PopupDialog.YES_NO_OPTION); + + if (answer == PopupDialog.YES_OPTION) + { + new ProtocolRegistrationThread(uri, provider).start(); + } + } + + /** + * The point of implementing a service listener here is so that we would + * only register our own uri handling service and thus only handle URIs + * while the factory is available as an OSGi service. We remove ourselves + * when our factory unregisters its service reference. + * + * @param event the OSGi ServiceEvent + */ + public void serviceChanged(ServiceEvent event) + { + Object sourceService = + JabberActivator.bundleContext. + getService(event.getServiceReference()); + + // ignore anything but our protocol factory. + if (sourceService != protoFactory) + { + return; + } + + switch (event.getType()) + { + case ServiceEvent.REGISTERED: + // our factory has just been registered as a service ... + registerHandlerService(); + break; + case ServiceEvent.UNREGISTERING: + // our factory just died - seppuku. + unregisterHandlerService(); + break; + default: + // we don't care. + break; + } + } + + /** + * Uses the UIService to show an error message and log and + * exception. + * + * @param message the message that we'd like to show to the user. + * @param exc the exception that we'd like to log + */ + private void showErrorMessage(String message, Exception exc) + { + JabberActivator.getUIService().getPopupDialog().showMessagePopupDialog( + message, "Failed to create chat!", PopupDialog.ERROR_MESSAGE); + logger.error(message, exc); + } + + /** + * We use this class when launching a provider registration by ourselves in + * order to track for provider registration states and retry uri handling, + * once the provider is registered. + * + */ + private class ProtocolRegistrationThread + extends Thread + implements RegistrationStateChangeListener + { + + private ProtocolProviderService handlerProvider = null; + + /** + * The URI that we'd need to chat. + */ + private String uri = null; + + /** + * Configures this thread register our parent provider and re-attempt + * connection to the specified uri. + * + * @param uri the uri that we need to handle. + * @param handlerProvider the provider that we are going to make + * register and that we are going to use to handle the + * uri. + */ + public ProtocolRegistrationThread(String uri, + ProtocolProviderService handlerProvider) + { + super("UriHandlerProviderRegistrationThread:uri=" + uri); + this.uri = uri; + this.handlerProvider = handlerProvider; + } + + /** + * Starts the registration process, ads this class as a registration + * listener and then tries to rehandle the uri this thread was initiaded + * with. + */ + @Override + public void run() + { + handlerProvider.addRegistrationStateChangeListener(this); + + try + { + handlerProvider.register(JabberActivator.getUIService() + .getDefaultSecurityAuthority(handlerProvider)); + } + catch (OperationFailedException exc) + { + logger.error("Failed to manually register provider."); + logger.warn(exc.getMessage(), exc); + } + } + + /** + * If the parent provider passes into the registration state, the method + * re-handles the URI that this thread was initiated with. The method + * would only rehandle the uri if the event shows successful + * registration. It would ignore intermediate states such as + * REGISTERING. Disconnection and failure events would simply cause this + * listener to remove itself from the list of registration listeners. + * + * @param evt the RegistrationStateChangeEvent that this thread + * was initiated with. + */ + public void registrationStateChanged(RegistrationStateChangeEvent evt) + { + if (evt.getNewState() == RegistrationState.REGISTERED) + { + Thread uriRehandleThread = new Thread() + { + public void run() + { + handleUri(uri); + } + }; + + uriRehandleThread.setName("UriRehandleThread:uri=" + uri); + uriRehandleThread.start(); + } + + // we're only interested in a single event so we stop listening + // (unless this was a REGISTERING notification) + if (evt.getNewState() == RegistrationState.REGISTERING) + return; + + handlerProvider.removeRegistrationStateChangeListener(this); + } + } + + /** + * Returns the default provider that we are supposed to handle URIs through + * or null if there aren't any. Depending on the implementation this method + * may require user intervention so make sure you don't rely on a quick + * outcome when chatting it. + * + * @param uri the uri that we'd like to handle with the provider that we are + * about to select. + * + * @return the provider that we should handle URIs through. + * + * @throws OperationFailedException with code OPERATION_CANCELED if + * the users. + */ + public ProtocolProviderService selectHandlingProvider(String uri) + throws OperationFailedException + { + ArrayList registeredAccounts = + protoFactory.getRegisteredAccounts(); + + // if we don't have any providers - return null. + if (registeredAccounts.size() == 0) + { + return null; + } + + // if we only have one provider - select it + if (registeredAccounts.size() == 1) + { + ServiceReference providerReference = + protoFactory.getProviderForAccount(registeredAccounts.get(0)); + + ProtocolProviderService provider = + (ProtocolProviderService) JabberActivator.bundleContext + .getService(providerReference); + + return provider; + } + + // otherwise - ask the user. + ArrayList providers = + new ArrayList(); + for (AccountID accountID : registeredAccounts) + { + ServiceReference providerReference = + protoFactory.getProviderForAccount(accountID); + + ProtocolProviderService provider = + (ProtocolProviderService) JabberActivator.bundleContext + .getService(providerReference); + + providers.add(new ProviderComboBoxEntry(provider)); + } + + Object result = + JabberActivator.getUIService().getPopupDialog().showInputPopupDialog( + "Please select the account that you would like \n" + + "to use to chat with " + uri, "Account Selection", + PopupDialog.OK_CANCEL_OPTION, providers.toArray(), + providers.get(0)); + + if (result == null) + { + throw new OperationFailedException("Operation cancelled", + OperationFailedException.OPERATION_CANCELED); + } + + return ((ProviderComboBoxEntry) result).provider; + } + + /** + * A class that we use to wrap providers before showing them to the user + * through a selection popup dialog from the UIService. + */ + private static class ProviderComboBoxEntry + { + public final ProtocolProviderService provider; + + public ProviderComboBoxEntry(ProtocolProviderService provider) + { + this.provider = provider; + } + + /** + * Returns a human readable String representing the provider + * encapsulated by this class. + * + * @return a human readable string representing the provider. + */ + @Override + public String toString() + { + return provider.getAccountID().getAccountAddress(); + } + } + + /** + * Waiting for the provider to bcome online and then handle the uri. + */ + private class ProviderStatusListener + implements ProviderPresenceStatusListener + { + private String uri; + private OperationSetPresence parentOpSet; + + public ProviderStatusListener(String uri, OperationSetPresence parentOpSet) + { + this.uri = uri; + this.parentOpSet = parentOpSet; + } + + public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) + { + if(evt.getNewStatus().isOnline()) + { + parentOpSet.removeProviderPresenceStatusListener(this); + handleUri(uri); + } + } + + public void providerStatusMessageChanged(java.beans.PropertyChangeEvent evt) + { + } + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf index 21872f6..6aa6e2d 100755 --- a/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/jabber/jabber.provider.manifest.mf @@ -27,6 +27,8 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.protocol.jabberconstants, net.java.sip.communicator.service.protocol.event, net.java.sip.communicator.service.protocol.whiteboardobjects, + net.java.sip.communicator.service.argdelegation, + net.java.sip.communicator.service.gui, net.java.sip.communicator.service.media, net.java.sip.communicator.service.media.event, org.xmlpull.v1, -- cgit v1.1