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/protocol/jabber/UriHandlerJabberImpl.java | 689 +++++++++++++++++++++ 1 file changed, 689 insertions(+) create mode 100644 src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java (limited to 'src/net/java/sip/communicator/impl/protocol/jabber/UriHandlerJabberImpl.java') 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) + { + } + } +} -- cgit v1.1