aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/service/protocol/AccountManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/java/sip/communicator/service/protocol/AccountManager.java')
-rw-r--r--src/net/java/sip/communicator/service/protocol/AccountManager.java2166
1 files changed, 1083 insertions, 1083 deletions
diff --git a/src/net/java/sip/communicator/service/protocol/AccountManager.java b/src/net/java/sip/communicator/service/protocol/AccountManager.java
index c608092..3ce85dc 100644
--- a/src/net/java/sip/communicator/service/protocol/AccountManager.java
+++ b/src/net/java/sip/communicator/service/protocol/AccountManager.java
@@ -1,1083 +1,1083 @@
-/*
- * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
- *
- * Copyright @ 2015 Atlassian Pty Ltd
- *
- * 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 net.java.sip.communicator.service.protocol;
-
-import java.util.*;
-
-import net.java.sip.communicator.service.credentialsstorage.*;
-import net.java.sip.communicator.service.protocol.event.*;
-import net.java.sip.communicator.util.*;
-import net.java.sip.communicator.util.Base64; //disambiguate from java.util.Base64
-
-import org.jitsi.service.configuration.*;
-import org.osgi.framework.*;
-
-/**
- * Represents an implementation of <tt>AccountManager</tt> which loads the
- * accounts in a separate thread.
- *
- * @author Lyubomir Marinov
- * @author Yana Stamcheva
- */
-public class AccountManager
-{
- /**
- * The delay in milliseconds the background <tt>Thread</tt> loading the
- * stored accounts should wait before dying so that it doesn't get recreated
- * for each <tt>ProtocolProviderFactory</tt> registration.
- */
- private static final long LOAD_STORED_ACCOUNTS_TIMEOUT = 30000;
-
- /**
- * The <tt>BundleContext</tt> this service is registered in.
- */
- private final BundleContext bundleContext;
-
- /**
- * The <tt>AccountManagerListener</tt>s currently interested in the
- * events fired by this manager.
- */
- private final List<AccountManagerListener> listeners =
- new LinkedList<AccountManagerListener>();
-
- /**
- * The queue of <tt>ProtocolProviderFactory</tt> services awaiting their
- * stored accounts to be loaded.
- */
- private final Queue<ProtocolProviderFactory> loadStoredAccountsQueue =
- new LinkedList<ProtocolProviderFactory>();
-
- /**
- * The <tt>Thread</tt> loading the stored accounts of the
- * <tt>ProtocolProviderFactory</tt> services waiting in
- * {@link #loadStoredAccountsQueue}.
- */
- private Thread loadStoredAccountsThread;
-
- /**
- * The <tt>Logger</tt> used by this <tt>AccountManagerImpl</tt> instance for
- * logging output.
- */
- private final Logger logger = Logger.getLogger(AccountManager.class);
-
- /**
- * The list of <tt>AccountID</tt>s, corresponding to all stored accounts.
- */
- private final Vector<AccountID> storedAccounts = new Vector<AccountID>();
-
- /**
- * The prefix of the account unique identifier.
- */
- private static final String ACCOUNT_UID_PREFIX = "acc";
-
- /**
- * Initializes a new <tt>AccountManagerImpl</tt> instance loaded in a
- * specific <tt>BundleContext</tt> (in which the caller will usually
- * later register it).
- *
- * @param bundleContext the <tt>BundleContext</tt> in which the new
- * instance is loaded (and in which the caller will usually later
- * register it as a service)
- */
- public AccountManager(BundleContext bundleContext)
- {
- this.bundleContext = bundleContext;
-
- this.bundleContext.addServiceListener(new ServiceListener()
- {
- public void serviceChanged(ServiceEvent serviceEvent)
- {
- AccountManager.this.serviceChanged(serviceEvent);
- }
- });
- }
-
- /**
- * Implements AccountManager#addListener(AccountManagerListener).
- * @param listener the <tt>AccountManagerListener</tt> to add
- */
- public void addListener(AccountManagerListener listener)
- {
- synchronized (listeners)
- {
- if (!listeners.contains(listener))
- listeners.add(listener);
- }
- }
-
- /**
- * Loads the accounts stored for a specific
- * <tt>ProtocolProviderFactory</tt>.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to load the
- * stored accounts of
- */
- private void doLoadStoredAccounts(ProtocolProviderFactory factory)
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> accounts
- = configService.getPropertyNamesByPrefix(factoryPackage, true);
-
- if (logger.isDebugEnabled())
- logger.debug("Discovered " + accounts.size() + " stored "
- + factoryPackage + " accounts");
-
- for (Iterator<String> storedAccountIter = accounts.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- // If the property is not related to an account we skip it.
- int dotIndex = storedAccount.lastIndexOf(".");
- if (!storedAccount.substring(dotIndex + 1)
- .startsWith(ACCOUNT_UID_PREFIX))
- continue;
-
- if (logger.isDebugEnabled())
- logger.debug("Loading account " + storedAccount);
-
- List<String> storedAccountProperties =
- configService.getPropertyNamesByPrefix(storedAccount, false);
- Map<String, String> accountProperties =
- new Hashtable<String, String>();
- boolean disabled = false;
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- int prefLen = storedAccount.length() + 1;
- for (Iterator<String> storedAccountPropertyIter
- = storedAccountProperties.iterator();
- storedAccountPropertyIter.hasNext();)
- {
- String property = storedAccountPropertyIter.next();
- String value = configService.getString(property);
-
- //strip the package prefix
- if(prefLen > property.length())
- continue;
-
- property = property.substring(prefLen);
-
- if (ProtocolProviderFactory.IS_ACCOUNT_DISABLED.equals(property))
- disabled = Boolean.parseBoolean(value);
- // Decode passwords.
- else if (ProtocolProviderFactory.PASSWORD.equals(property)
- && !credentialsStorage.isStoredEncrypted(storedAccount))
- {
- if ((value != null) && value.length() != 0)
- {
-
- /*
- * TODO Converting byte[] to String using the platform's
- * default charset may result in an invalid password.
- */
- value = new String(Base64.decode(value));
- }
- }
-
- if (value != null)
- accountProperties.put(property, value);
- }
-
- try
- {
- AccountID accountID = factory.createAccount(accountProperties);
-
- // If for some reason the account id is not created we move to
- // the next account.
- if (accountID == null)
- continue;
-
- synchronized (storedAccounts)
- {
- storedAccounts.add(accountID);
- }
- if (!disabled)
- factory.loadAccount(accountID);
- }
- catch (Exception ex)
- {
- /*
- * Swallow the exception in order to prevent a single account
- * from halting the loading of subsequent accounts.
- */
- logger.error("Failed to load account " + accountProperties, ex);
- }
- catch (ExceptionInInitializerError ex)
- {
- // In case we fail to instantiate the ProtocolProviderService.
- logger.error(
- "Failed to create account service instance for account "
- + accountProperties, ex);
- }
- }
- }
-
- /**
- * Notifies the registered {@link #listeners} that the stored accounts of a
- * specific <tt>ProtocolProviderFactory</tt> have just been loaded.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which had its
- * stored accounts just loaded
- */
- private void fireStoredAccountsLoaded(ProtocolProviderFactory factory)
- {
- AccountManagerListener[] listeners;
- synchronized (this.listeners)
- {
- listeners =
- this.listeners
- .toArray(new AccountManagerListener[this.listeners.size()]);
- }
-
- int listenerCount = listeners.length;
- if (listenerCount > 0)
- {
- AccountManagerEvent event =
- new AccountManagerEvent(this,
- AccountManagerEvent.STORED_ACCOUNTS_LOADED, factory);
-
- for (int listenerIndex = 0;
- listenerIndex < listenerCount; listenerIndex++)
- {
- listeners[listenerIndex].handleAccountManagerEvent(event);
- }
- }
- }
-
- /**
- * Returns the package name of the <tt>factory</tt>.
- * @param factory the factory which package will be returned.
- * @return the package name of the <tt>factory</tt>.
- */
- public String getFactoryImplPackageName(ProtocolProviderFactory factory)
- {
- String className = factory.getClass().getName();
-
- return className.substring(0, className.lastIndexOf('.'));
- }
-
- /**
- * Check for stored accounts for the supplied <tt>protocolName</tt>.
- * @param protocolName the protocol name to check for
- * @param includeHidden whether to include hidden providers
- * @return <tt>true</tt> if there is any account stored in configuration
- * service with <tt>protocolName</tt>, <tt>false</tt> otherwise.
- */
- public boolean hasStoredAccounts(String protocolName, boolean includeHidden)
- {
- return hasStoredAccount(protocolName, includeHidden, null);
- }
-
- /**
- * Checks whether a stored account with <tt>userID</tt> is stored
- * in configuration.
- *
- * @param protocolName the protocol name
- * @param includeHidden whether to check hidden providers
- * @param userID the user id to check.
- * @return <tt>true</tt> if there is any account stored in configuration
- * service with <tt>protocolName</tt> and <tt>userID</tt>,
- * <tt>false</tt> otherwise.
- */
- public boolean hasStoredAccount(String protocolName,
- boolean includeHidden,
- String userID)
- {
- Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
- = ServiceUtils.getServiceReferences(
- bundleContext,
- ProtocolProviderFactory.class);
- boolean hasStoredAccounts = false;
-
- if (!factoryRefs.isEmpty())
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
-
- for (ServiceReference<ProtocolProviderFactory> factoryRef
- : factoryRefs)
- {
- ProtocolProviderFactory factory
- = bundleContext.getService(factoryRef);
-
- if ((protocolName != null)
- && !protocolName.equals(factory.getProtocolName()))
- {
- continue;
- }
-
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> storedAccounts
- = configService
- .getPropertyNamesByPrefix(factoryPackage + ".acc",
- false);
-
- /* Ignore the hidden accounts. */
- for (Iterator<String> storedAccountIter =
- storedAccounts.iterator(); storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
- List<String> storedAccountProperties =
- configService.getPropertyNamesByPrefix(storedAccount,
- true);
- boolean hidden = false;
- String accountUserID = null;
-
- if (!includeHidden || userID != null)
- {
- for (Iterator<String> storedAccountPropertyIter =
- storedAccountProperties.iterator();
- storedAccountPropertyIter.hasNext();)
- {
- String property = storedAccountPropertyIter.next();
- String value = configService.getString(property);
-
- property = stripPackagePrefix(property);
-
- if (ProtocolProviderFactory.IS_PROTOCOL_HIDDEN
- .equals(property))
- {
- hidden = (value != null);
- }
- else if (ProtocolProviderFactory.USER_ID
- .equals(property))
- {
- accountUserID = value;
- }
- }
- }
-
- if (includeHidden || !hidden)
- {
- if(accountUserID != null
- && userID != null
- && userID.equals(accountUserID))
- {
- hasStoredAccounts = true;
- break;
- }
- else if(userID == null)
- {
- hasStoredAccounts = true;
- break;
- }
- }
- }
-
- if (hasStoredAccounts || (protocolName != null))
- {
- break;
- }
- }
- }
- return hasStoredAccounts;
- }
-
- /**
- * Searches for stored account with <tt>uid</tt> in stored
- * configuration. The <tt>uid</tt> is the one generated when creating
- * accounts with prefix <tt>ACCOUNT_UID_PREFIX</tt>.
- *
- * @return <tt>AccountID</tt> if there is any account stored in configuration
- * service with <tt>uid</tt>,
- * <tt>null</tt> otherwise.
- */
- public AccountID findAccountID(String uid)
- {
- Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
- = ServiceUtils.getServiceReferences(
- bundleContext,
- ProtocolProviderFactory.class);
-
- if (!factoryRefs.isEmpty())
- {
- ConfigurationService configService
- = ProtocolProviderActivator.getConfigurationService();
-
- for (ServiceReference<ProtocolProviderFactory> factoryRef
- : factoryRefs)
- {
- ProtocolProviderFactory factory
- = bundleContext.getService(factoryRef);
-
- String factoryPackage = getFactoryImplPackageName(factory);
- List<String> storedAccountsProps
- = configService
- .getPropertyNamesByPrefix(factoryPackage, true);
-
- for (Iterator<String> storedAccountIter =
- storedAccountsProps.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- if(!storedAccount.endsWith(uid))
- continue;
-
- String accountUID = configService.getString(
- storedAccount //node id
- + "." + ProtocolProviderFactory.ACCOUNT_UID);// propname
-
- for(AccountID acc : storedAccounts)
- {
- if(acc.getAccountUniqueID().equals(accountUID))
- return acc;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Loads the accounts stored for a specific
- * <tt>ProtocolProviderFactory</tt> and notifies the registered
- * {@link #listeners} that the stored accounts of the specified
- * <tt>factory</tt> have just been loaded
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to load the
- * stored accounts of
- */
- private void loadStoredAccounts(ProtocolProviderFactory factory)
- {
- doLoadStoredAccounts(factory);
-
- fireStoredAccountsLoaded(factory);
- }
-
- /**
- * Notifies this manager that a specific
- * <tt>ProtocolProviderFactory</tt> has been registered as a service.
- * The current implementation queues the specified <tt>factory</tt> to
- * have its stored accounts as soon as possible.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which has been
- * registered as a service.
- */
- private void protocolProviderFactoryRegistered(
- ProtocolProviderFactory factory)
- {
- queueLoadStoredAccounts(factory);
- }
-
- /**
- * Queues a specific <tt>ProtocolProviderFactory</tt> to have its stored
- * accounts loaded as soon as possible.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> to be queued for
- * loading its stored accounts as soon as possible
- */
- private void queueLoadStoredAccounts(ProtocolProviderFactory factory)
- {
- synchronized (loadStoredAccountsQueue)
- {
- loadStoredAccountsQueue.add(factory);
- loadStoredAccountsQueue.notifyAll();
-
- if (loadStoredAccountsThread == null)
- {
- loadStoredAccountsThread = new Thread()
- {
- @Override
- public void run()
- {
- runInLoadStoredAccountsThread();
- }
- };
- loadStoredAccountsThread.setDaemon(true);
- loadStoredAccountsThread.setName(
- "AccountManager.loadStoredAccounts");
- loadStoredAccountsThread.start();
- }
- }
- }
-
- /**
- * Implements AccountManager#removeListener(AccountManagerListener).
- * @param listener the <tt>AccountManagerListener</tt> to remove
- */
- public void removeListener(AccountManagerListener listener)
- {
- synchronized (listeners)
- {
- listeners.remove(listener);
- }
- }
-
- /**
- * Running in {@link #loadStoredAccountsThread}, loads the stored accounts
- * of the <tt>ProtocolProviderFactory</tt> services waiting in
- * {@link #loadStoredAccountsQueue}
- */
- private void runInLoadStoredAccountsThread()
- {
- boolean interrupted = false;
- while (!interrupted)
- {
- try
- {
- ProtocolProviderFactory factory;
-
- synchronized (loadStoredAccountsQueue)
- {
- factory = loadStoredAccountsQueue.poll();
- if (factory == null)
- {
- /*
- * Technically, we should be handing spurious wakeups.
- * However, we cannot check the condition in a queue.
- * Anyway, we just want to keep this Thread alive long
- * enough to allow it to not be re-created multiple
- * times and not handing a spurious wakeup will just
- * cause such an inconvenience.
- */
- try
- {
- loadStoredAccountsQueue
- .wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
- }
- catch (InterruptedException ex)
- {
- logger
- .warn(
- "The loading of the stored accounts has"
- + " been interrupted",
- ex);
- interrupted = true;
- break;
- }
- factory = loadStoredAccountsQueue.poll();
- }
- if (factory != null)
- loadStoredAccountsQueue.notifyAll();
- }
-
- if (factory != null)
- {
- try
- {
- loadStoredAccounts(factory);
- }
- catch (Exception ex)
- {
-
- /*
- * Swallow the exception in order to prevent a single
- * factory from halting the loading of subsequent
- * factories.
- */
- logger.error("Failed to load accounts for " + factory,
- ex);
- }
- }
- }
- finally
- {
- synchronized (loadStoredAccountsQueue)
- {
- if (!interrupted && (loadStoredAccountsQueue.size() <= 0))
- {
- if (loadStoredAccountsThread == Thread.currentThread())
- {
- loadStoredAccountsThread = null;
- loadStoredAccountsQueue.notifyAll();
- }
- break;
- }
- }
- }
- }
- }
-
- /**
- * Notifies this manager that an OSGi service has changed. The current
- * implementation tracks the registrations of
- * <tt>ProtocolProviderFactory</tt> services in order to queue them for
- * loading their stored accounts.
- *
- * @param serviceEvent the <tt>ServiceEvent</tt> containing the event
- * data
- */
- private void serviceChanged(ServiceEvent serviceEvent)
- {
- switch (serviceEvent.getType())
- {
- case ServiceEvent.REGISTERED:
- Object service
- = bundleContext.getService(serviceEvent.getServiceReference());
-
- if (service instanceof ProtocolProviderFactory)
- {
- protocolProviderFactoryRegistered(
- (ProtocolProviderFactory) service);
- }
- break;
- default:
- break;
- }
- }
-
- /**
- * Stores an account represented in the form of an <tt>AccountID</tt>
- * created by a specific <tt>ProtocolProviderFactory</tt>.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which created the
- * account to be stored
- * @param accountID the account in the form of <tt>AccountID</tt> to be
- * stored
- * @throws OperationFailedException if anything goes wrong while storing the
- * account
- */
- public void storeAccount(
- ProtocolProviderFactory factory,
- AccountID accountID)
- throws OperationFailedException
- {
- synchronized (storedAccounts)
- {
- if (!storedAccounts.contains(accountID))
- storedAccounts.add(accountID);
- }
-
- ConfigurationService configurationService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
-
- String accountNodeName
- = getAccountNodeName( factory,
- accountID.getAccountUniqueID() );
-
- Map<String, Object> configurationProperties
- = new HashMap<String, Object>();
-
- // Create a unique node name of the properties node that will contain
- // this account's properties.
- if (accountNodeName == null)
- {
- accountNodeName
- = ACCOUNT_UID_PREFIX + Long.toString(System.currentTimeMillis());
-
- // set a value for the persistent node so that we could later
- // retrieve it as a property
- configurationProperties.put(
- factoryPackage /* prefix */ + "." + accountNodeName,
- accountNodeName);
-
- // register the account in the configuration service.
- // we register all the properties in the following hierarchy
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- configurationProperties.put(factoryPackage// prefix
- + "." + accountNodeName // node name for the account id
- + "." + ProtocolProviderFactory.ACCOUNT_UID, // propname
- accountID.getAccountUniqueID()); // value
- }
-
- // store the rest of the properties
- Map<String, String> accountProperties = accountID.getAccountProperties();
-
- for (Map.Entry<String, String> entry : accountProperties.entrySet())
- {
- String property = entry.getKey();
- String value = entry.getValue();
- String secureStorePrefix = null;
-
- // If the property is a password, store it securely.
- if (property.equals(ProtocolProviderFactory.PASSWORD))
- {
- String accountPrefix = factoryPackage + "." + accountNodeName;
- secureStorePrefix = accountPrefix;
- }
- else if(property.endsWith("." + ProtocolProviderFactory.PASSWORD))
- {
- secureStorePrefix = factoryPackage + "." + accountNodeName +
- "." + property.substring(0, property.lastIndexOf("."));
- }
-
- if(secureStorePrefix != null)
- {
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
-
- // encrypt and store
- if ((value != null)
- && (value.length() != 0)
- && !credentialsStorage.storePassword(
- secureStorePrefix,
- value))
- {
- throw
- new OperationFailedException(
- "CredentialsStorageService failed to"
- + " storePassword",
- OperationFailedException.GENERAL_ERROR);
- }
- }
- else
- {
- configurationProperties.put(
- factoryPackage // prefix
- + "." + accountNodeName // a unique node name for the account id
- + "." + property, // propname
- value); // value
- }
- }
-
- // clear the password if missing property, modification can request
- // password delete
- if(!accountProperties.containsKey(ProtocolProviderFactory.PASSWORD)
- && // And only if it's not stored already in encrypted form.
- // Account registration object clears also this property
- // in order to forget the password
- !configurationProperties.containsKey(
- factoryPackage+"."+accountNodeName+".ENCRYPTED_PASSWORD"))
- {
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
- credentialsStorage.removePassword(
- factoryPackage + "." + accountNodeName);
- }
-
- if (configurationProperties.size() > 0)
- configurationService.setProperties(configurationProperties);
-
- if (logger.isDebugEnabled())
- logger.debug("Stored account for id " + accountID.getAccountUniqueID()
- + " for package " + factoryPackage);
- }
-
- /**
- * Gets account node name under which account configuration properties are
- * stored.
- *
- * @param factory account's protocol provider factory
- * @param accountUID account for which the prefix will be returned
- * @return configuration prefix for given <tt>accountID</tt> if exists or
- * <tt>null</tt> otherwise
- */
- public String getAccountNodeName( ProtocolProviderFactory factory,
- String accountUID )
- {
- ConfigurationService configurationService
- = ProtocolProviderActivator.getConfigurationService();
- String factoryPackage = getFactoryImplPackageName(factory);
-
- // First check if such accountID already exists in the configuration.
- List<String> storedAccounts =
- configurationService.getPropertyNamesByPrefix(factoryPackage, true);
- String accountNodeName = null;
-
- for (Iterator<String> storedAccountIter = storedAccounts.iterator();
- storedAccountIter.hasNext();)
- {
- String storedAccount = storedAccountIter.next();
-
- // If the property is not related to an account we skip it.
- int dotIndex = storedAccount.lastIndexOf(".");
- if (!storedAccount.substring(dotIndex + 1)
- .startsWith(ACCOUNT_UID_PREFIX))
- continue;
-
- String storedAccountUID
- = configurationService.getString(
- storedAccount + "." + ProtocolProviderFactory.ACCOUNT_UID);
-
- if(storedAccountUID == null)
- continue;
-
- if (storedAccountUID.equals(accountUID))
- accountNodeName = configurationService.getString(storedAccount);
- }
- return accountNodeName;
- }
-
- /**
- * Removes the account with <tt>accountID</tt> from the set of accounts
- * that are persistently stored inside the configuration service.
- *
- * @param factory the <tt>ProtocolProviderFactory</tt> which created the
- * account to be stored
- * @param accountID the AccountID of the account to remove.
- * @return true if an account has been removed and false otherwise.
- */
- public boolean removeStoredAccount(ProtocolProviderFactory factory,
- AccountID accountID)
- {
- synchronized (storedAccounts)
- {
- if (storedAccounts.contains(accountID))
- storedAccounts.remove(accountID);
- }
-
- /*
- * We're already doing it in #unloadAccount(AccountID) - we're figuring
- * out the ProtocolProviderFactory by the AccountID.
- */
- if (factory == null)
- {
- factory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
- }
-
- String factoryPackage = getFactoryImplPackageName(factory);
-
- // remove the stored password explicitly using credentials service
- CredentialsStorageService credentialsStorage
- = ServiceUtils.getService(
- bundleContext,
- CredentialsStorageService.class);
- String accountPrefix =
- ProtocolProviderFactory.findAccountPrefix(bundleContext, accountID,
- factoryPackage);
-
- credentialsStorage.removePassword(accountPrefix);
-
- ConfigurationService configurationService
- = ServiceUtils.getService(
- bundleContext,
- ConfigurationService.class);
- //first retrieve all accounts that we've registered
- List<String> storedAccounts
- = configurationService.getPropertyNamesByPrefix(
- factoryPackage, true);
-
- //find an account with the corresponding id.
- for (String accountRootPropertyName : storedAccounts)
- {
- //unregister the account in the configuration service.
- //all the properties must have been registered in the following
- //hierarchy:
- //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
- String accountUID = configurationService.getString(
- accountRootPropertyName //node id
- + "." + ProtocolProviderFactory.ACCOUNT_UID); // propname
-
- if (accountID.getAccountUniqueID().equals(accountUID))
- {
- //retrieve the names of all properties registered for the
- //current account.
- List<String> accountPropertyNames
- = configurationService.getPropertyNamesByPrefix(
- accountRootPropertyName, false);
-
- //set all account properties to null in order to remove them.
- for (String propName : accountPropertyNames)
- configurationService.setProperty(propName, null);
-
- //and now remove the parent too.
- configurationService.setProperty(accountRootPropertyName, null);
- return true;
- }
- }
- return false;
- }
-
- /**
- * Removes all accounts which have been persistently stored.
- *
- * @see #removeStoredAccount(ProtocolProviderFactory, AccountID)
- */
- public void removeStoredAccounts()
- {
- synchronized (loadStoredAccountsQueue)
- {
- /*
- * Wait for the Thread which loads the stored account to complete so
- * that we can be sure later on that it will not load a stored
- * account while we are deleting it or another one for that matter.
- */
- boolean interrupted = false;
-
- while (loadStoredAccountsThread != null)
- try
- {
- loadStoredAccountsQueue.wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
- }
- catch (InterruptedException ie)
- {
- interrupted = true;
- }
- if (interrupted)
- Thread.currentThread().interrupt();
-
- synchronized (this.storedAccounts)
- {
- AccountID[] storedAccounts
- = this.storedAccounts.toArray(
- new AccountID[this.storedAccounts.size()]);
-
- for (AccountID storedAccount : storedAccounts)
- {
- ProtocolProviderFactory ppf
- = ProtocolProviderActivator.getProtocolProviderFactory(
- storedAccount.getProtocolName());
-
- if (ppf != null)
- ppf.uninstallAccount(storedAccount);
- }
- }
- }
- }
-
- /**
- * Returns an <tt>Iterator</tt> over a list of all stored
- * <tt>AccountID</tt>s. The list of stored accounts include all registered
- * accounts and all disabled accounts. In other words in this list we could
- * find accounts that aren't loaded.
- * <p>
- * In order to check if an account is already loaded please use the
- * #isAccountLoaded(AccountID accountID) method. To load an account use the
- * #loadAccount(AccountID accountID) method.
- *
- * @return an <tt>Iterator</tt> over a list of all stored
- * <tt>AccountID</tt>s
- */
- public Collection<AccountID> getStoredAccounts()
- {
- synchronized (storedAccounts)
- {
- return new Vector<AccountID>(storedAccounts);
- }
- }
-
- /**
- * Loads the account corresponding to the given <tt>AccountID</tt>. An
- * account is loaded when its <tt>ProtocolProviderService</tt> is registered
- * in the bundle context. This method is meant to load the account through
- * the corresponding <tt>ProtocolProviderFactory</tt>.
- *
- * @param accountID the identifier of the account to load
- * @throws OperationFailedException if anything goes wrong while loading the
- * account corresponding to the specified <tt>accountID</tt>
- */
- public void loadAccount(AccountID accountID)
- throws OperationFailedException
- {
- // If the account with the given id is already loaded we have nothing
- // to do here.
- if (isAccountLoaded(accountID))
- return;
-
- ProtocolProviderFactory providerFactory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
-
- if(providerFactory.loadAccount(accountID))
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(false));
- // Finally store the modified properties.
- storeAccount(providerFactory, accountID);
- }
- }
-
- /**
- * Unloads the account corresponding to the given <tt>AccountID</tt>. An
- * account is unloaded when its <tt>ProtocolProviderService</tt> is
- * unregistered in the bundle context. This method is meant to unload the
- * account through the corresponding <tt>ProtocolProviderFactory</tt>.
- *
- * @param accountID the identifier of the account to load
- * @throws OperationFailedException if anything goes wrong while unloading
- * the account corresponding to the specified <tt>accountID</tt>
- */
- public void unloadAccount(AccountID accountID)
- throws OperationFailedException
- {
- // If the account with the given id is already unloaded we have nothing
- // to do here.
- if (!isAccountLoaded(accountID))
- return;
-
- ProtocolProviderFactory providerFactory
- = ProtocolProviderActivator.getProtocolProviderFactory(
- accountID.getProtocolName());
-
- // Obtain the protocol provider.
- ServiceReference<ProtocolProviderService> serRef
- = providerFactory.getProviderForAccount(accountID);
-
- // If there's no such provider we have nothing to do here.
- if (serRef == null)
- return;
-
- ProtocolProviderService protocolProvider
- = bundleContext.getService(serRef);
-
- // Set the account icon path for unloaded accounts.
- String iconPathProperty = accountID.getAccountPropertyString(
- ProtocolProviderFactory.ACCOUNT_ICON_PATH);
-
- if (iconPathProperty == null)
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.ACCOUNT_ICON_PATH,
- protocolProvider.getProtocolIcon()
- .getIconPath(ProtocolIcon.ICON_SIZE_32x32));
- }
-
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(true));
-
- if (!providerFactory.unloadAccount(accountID))
- {
- accountID.putAccountProperty(
- ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
- String.valueOf(false));
- }
- // Finally store the modified properties.
- storeAccount(providerFactory, accountID);
- }
-
- /**
- * Checks if the account corresponding to the given <tt>accountID</tt> is
- * loaded. An account is loaded if its <tt>ProtocolProviderService</tt> is
- * registered in the bundle context. By default all accounts are loaded.
- * However the user could manually unload an account, which would be
- * unregistered from the bundle context, but would remain in the
- * configuration file.
- *
- * @param accountID the identifier of the account to load
- * @return <tt>true</tt> to indicate that the account with the given
- * <tt>accountID</tt> is loaded, <tt>false</tt> - otherwise
- */
- public boolean isAccountLoaded(AccountID accountID)
- {
- return storedAccounts.contains(accountID) && accountID.isEnabled();
- }
-
- private String stripPackagePrefix(String property)
- {
- int packageEndIndex = property.lastIndexOf('.');
-
- if (packageEndIndex != -1)
- property = property.substring(packageEndIndex + 1);
- return property;
- }
-}
+/*
+ * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * 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 net.java.sip.communicator.service.protocol;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.credentialsstorage.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Base64; //disambiguate from java.util.Base64
+
+import org.jitsi.service.configuration.*;
+import org.osgi.framework.*;
+
+/**
+ * Represents an implementation of <tt>AccountManager</tt> which loads the
+ * accounts in a separate thread.
+ *
+ * @author Lyubomir Marinov
+ * @author Yana Stamcheva
+ */
+public class AccountManager
+{
+ /**
+ * The delay in milliseconds the background <tt>Thread</tt> loading the
+ * stored accounts should wait before dying so that it doesn't get recreated
+ * for each <tt>ProtocolProviderFactory</tt> registration.
+ */
+ private static final long LOAD_STORED_ACCOUNTS_TIMEOUT = 30000;
+
+ /**
+ * The <tt>BundleContext</tt> this service is registered in.
+ */
+ private final BundleContext bundleContext;
+
+ /**
+ * The <tt>AccountManagerListener</tt>s currently interested in the
+ * events fired by this manager.
+ */
+ private final List<AccountManagerListener> listeners =
+ new LinkedList<AccountManagerListener>();
+
+ /**
+ * The queue of <tt>ProtocolProviderFactory</tt> services awaiting their
+ * stored accounts to be loaded.
+ */
+ private final Queue<ProtocolProviderFactory> loadStoredAccountsQueue =
+ new LinkedList<ProtocolProviderFactory>();
+
+ /**
+ * The <tt>Thread</tt> loading the stored accounts of the
+ * <tt>ProtocolProviderFactory</tt> services waiting in
+ * {@link #loadStoredAccountsQueue}.
+ */
+ private Thread loadStoredAccountsThread;
+
+ /**
+ * The <tt>Logger</tt> used by this <tt>AccountManagerImpl</tt> instance for
+ * logging output.
+ */
+ private final Logger logger = Logger.getLogger(AccountManager.class);
+
+ /**
+ * The list of <tt>AccountID</tt>s, corresponding to all stored accounts.
+ */
+ private final Vector<AccountID> storedAccounts = new Vector<AccountID>();
+
+ /**
+ * The prefix of the account unique identifier.
+ */
+ private static final String ACCOUNT_UID_PREFIX = "acc";
+
+ /**
+ * Initializes a new <tt>AccountManagerImpl</tt> instance loaded in a
+ * specific <tt>BundleContext</tt> (in which the caller will usually
+ * later register it).
+ *
+ * @param bundleContext the <tt>BundleContext</tt> in which the new
+ * instance is loaded (and in which the caller will usually later
+ * register it as a service)
+ */
+ public AccountManager(BundleContext bundleContext)
+ {
+ this.bundleContext = bundleContext;
+
+ this.bundleContext.addServiceListener(new ServiceListener()
+ {
+ public void serviceChanged(ServiceEvent serviceEvent)
+ {
+ AccountManager.this.serviceChanged(serviceEvent);
+ }
+ });
+ }
+
+ /**
+ * Implements AccountManager#addListener(AccountManagerListener).
+ * @param listener the <tt>AccountManagerListener</tt> to add
+ */
+ public void addListener(AccountManagerListener listener)
+ {
+ synchronized (listeners)
+ {
+ if (!listeners.contains(listener))
+ listeners.add(listener);
+ }
+ }
+
+ /**
+ * Loads the accounts stored for a specific
+ * <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to load the
+ * stored accounts of
+ */
+ private void doLoadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> accounts
+ = configService.getPropertyNamesByPrefix(factoryPackage, true);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Discovered " + accounts.size() + " stored "
+ + factoryPackage + " accounts");
+
+ for (Iterator<String> storedAccountIter = accounts.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ // If the property is not related to an account we skip it.
+ int dotIndex = storedAccount.lastIndexOf(".");
+ if (!storedAccount.substring(dotIndex + 1)
+ .startsWith(ACCOUNT_UID_PREFIX))
+ continue;
+
+ if (logger.isDebugEnabled())
+ logger.debug("Loading account " + storedAccount);
+
+ List<String> storedAccountProperties =
+ configService.getPropertyNamesByPrefix(storedAccount, false);
+ Map<String, String> accountProperties =
+ new Hashtable<String, String>();
+ boolean disabled = false;
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ int prefLen = storedAccount.length() + 1;
+ for (Iterator<String> storedAccountPropertyIter
+ = storedAccountProperties.iterator();
+ storedAccountPropertyIter.hasNext();)
+ {
+ String property = storedAccountPropertyIter.next();
+ String value = configService.getString(property);
+
+ //strip the package prefix
+ if(prefLen > property.length())
+ continue;
+
+ property = property.substring(prefLen);
+
+ if (ProtocolProviderFactory.IS_ACCOUNT_DISABLED.equals(property))
+ disabled = Boolean.parseBoolean(value);
+ // Decode passwords.
+ else if (ProtocolProviderFactory.PASSWORD.equals(property)
+ && !credentialsStorage.isStoredEncrypted(storedAccount))
+ {
+ if ((value != null) && value.length() != 0)
+ {
+
+ /*
+ * TODO Converting byte[] to String using the platform's
+ * default charset may result in an invalid password.
+ */
+ value = new String(Base64.decode(value));
+ }
+ }
+
+ if (value != null)
+ accountProperties.put(property, value);
+ }
+
+ try
+ {
+ AccountID accountID = factory.createAccount(accountProperties);
+
+ // If for some reason the account id is not created we move to
+ // the next account.
+ if (accountID == null)
+ continue;
+
+ synchronized (storedAccounts)
+ {
+ storedAccounts.add(accountID);
+ }
+ if (!disabled)
+ factory.loadAccount(accountID);
+ }
+ catch (Exception ex)
+ {
+ /*
+ * Swallow the exception in order to prevent a single account
+ * from halting the loading of subsequent accounts.
+ */
+ logger.error("Failed to load account " + accountProperties, ex);
+ }
+ catch (ExceptionInInitializerError ex)
+ {
+ // In case we fail to instantiate the ProtocolProviderService.
+ logger.error(
+ "Failed to create account service instance for account "
+ + accountProperties, ex);
+ }
+ }
+ }
+
+ /**
+ * Notifies the registered {@link #listeners} that the stored accounts of a
+ * specific <tt>ProtocolProviderFactory</tt> have just been loaded.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which had its
+ * stored accounts just loaded
+ */
+ private void fireStoredAccountsLoaded(ProtocolProviderFactory factory)
+ {
+ AccountManagerListener[] listeners;
+ synchronized (this.listeners)
+ {
+ listeners =
+ this.listeners
+ .toArray(new AccountManagerListener[this.listeners.size()]);
+ }
+
+ int listenerCount = listeners.length;
+ if (listenerCount > 0)
+ {
+ AccountManagerEvent event =
+ new AccountManagerEvent(this,
+ AccountManagerEvent.STORED_ACCOUNTS_LOADED, factory);
+
+ for (int listenerIndex = 0;
+ listenerIndex < listenerCount; listenerIndex++)
+ {
+ listeners[listenerIndex].handleAccountManagerEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Returns the package name of the <tt>factory</tt>.
+ * @param factory the factory which package will be returned.
+ * @return the package name of the <tt>factory</tt>.
+ */
+ public String getFactoryImplPackageName(ProtocolProviderFactory factory)
+ {
+ String className = factory.getClass().getName();
+
+ return className.substring(0, className.lastIndexOf('.'));
+ }
+
+ /**
+ * Check for stored accounts for the supplied <tt>protocolName</tt>.
+ * @param protocolName the protocol name to check for
+ * @param includeHidden whether to include hidden providers
+ * @return <tt>true</tt> if there is any account stored in configuration
+ * service with <tt>protocolName</tt>, <tt>false</tt> otherwise.
+ */
+ public boolean hasStoredAccounts(String protocolName, boolean includeHidden)
+ {
+ return hasStoredAccount(protocolName, includeHidden, null);
+ }
+
+ /**
+ * Checks whether a stored account with <tt>userID</tt> is stored
+ * in configuration.
+ *
+ * @param protocolName the protocol name
+ * @param includeHidden whether to check hidden providers
+ * @param userID the user id to check.
+ * @return <tt>true</tt> if there is any account stored in configuration
+ * service with <tt>protocolName</tt> and <tt>userID</tt>,
+ * <tt>false</tt> otherwise.
+ */
+ public boolean hasStoredAccount(String protocolName,
+ boolean includeHidden,
+ String userID)
+ {
+ Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
+ = ServiceUtils.getServiceReferences(
+ bundleContext,
+ ProtocolProviderFactory.class);
+ boolean hasStoredAccounts = false;
+
+ if (!factoryRefs.isEmpty())
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+
+ for (ServiceReference<ProtocolProviderFactory> factoryRef
+ : factoryRefs)
+ {
+ ProtocolProviderFactory factory
+ = bundleContext.getService(factoryRef);
+
+ if ((protocolName != null)
+ && !protocolName.equals(factory.getProtocolName()))
+ {
+ continue;
+ }
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> storedAccounts
+ = configService
+ .getPropertyNamesByPrefix(factoryPackage + ".acc",
+ false);
+
+ /* Ignore the hidden accounts. */
+ for (Iterator<String> storedAccountIter =
+ storedAccounts.iterator(); storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+ List<String> storedAccountProperties =
+ configService.getPropertyNamesByPrefix(storedAccount,
+ true);
+ boolean hidden = false;
+ String accountUserID = null;
+
+ if (!includeHidden || userID != null)
+ {
+ for (Iterator<String> storedAccountPropertyIter =
+ storedAccountProperties.iterator();
+ storedAccountPropertyIter.hasNext();)
+ {
+ String property = storedAccountPropertyIter.next();
+ String value = configService.getString(property);
+
+ property = stripPackagePrefix(property);
+
+ if (ProtocolProviderFactory.IS_PROTOCOL_HIDDEN
+ .equals(property))
+ {
+ hidden = (value != null);
+ }
+ else if (ProtocolProviderFactory.USER_ID
+ .equals(property))
+ {
+ accountUserID = value;
+ }
+ }
+ }
+
+ if (includeHidden || !hidden)
+ {
+ if(accountUserID != null
+ && userID != null
+ && userID.equals(accountUserID))
+ {
+ hasStoredAccounts = true;
+ break;
+ }
+ else if(userID == null)
+ {
+ hasStoredAccounts = true;
+ break;
+ }
+ }
+ }
+
+ if (hasStoredAccounts || (protocolName != null))
+ {
+ break;
+ }
+ }
+ }
+ return hasStoredAccounts;
+ }
+
+ /**
+ * Searches for stored account with <tt>uid</tt> in stored
+ * configuration. The <tt>uid</tt> is the one generated when creating
+ * accounts with prefix <tt>ACCOUNT_UID_PREFIX</tt>.
+ *
+ * @return <tt>AccountID</tt> if there is any account stored in configuration
+ * service with <tt>uid</tt>,
+ * <tt>null</tt> otherwise.
+ */
+ public AccountID findAccountID(String uid)
+ {
+ Collection<ServiceReference<ProtocolProviderFactory>> factoryRefs
+ = ServiceUtils.getServiceReferences(
+ bundleContext,
+ ProtocolProviderFactory.class);
+
+ if (!factoryRefs.isEmpty())
+ {
+ ConfigurationService configService
+ = ProtocolProviderActivator.getConfigurationService();
+
+ for (ServiceReference<ProtocolProviderFactory> factoryRef
+ : factoryRefs)
+ {
+ ProtocolProviderFactory factory
+ = bundleContext.getService(factoryRef);
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+ List<String> storedAccountsProps
+ = configService
+ .getPropertyNamesByPrefix(factoryPackage, true);
+
+ for (Iterator<String> storedAccountIter =
+ storedAccountsProps.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ if(!storedAccount.endsWith(uid))
+ continue;
+
+ String accountUID = configService.getString(
+ storedAccount //node id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID);// propname
+
+ for(AccountID acc : storedAccounts)
+ {
+ if(acc.getAccountUniqueID().equals(accountUID))
+ return acc;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Loads the accounts stored for a specific
+ * <tt>ProtocolProviderFactory</tt> and notifies the registered
+ * {@link #listeners} that the stored accounts of the specified
+ * <tt>factory</tt> have just been loaded
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to load the
+ * stored accounts of
+ */
+ private void loadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ doLoadStoredAccounts(factory);
+
+ fireStoredAccountsLoaded(factory);
+ }
+
+ /**
+ * Notifies this manager that a specific
+ * <tt>ProtocolProviderFactory</tt> has been registered as a service.
+ * The current implementation queues the specified <tt>factory</tt> to
+ * have its stored accounts as soon as possible.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which has been
+ * registered as a service.
+ */
+ private void protocolProviderFactoryRegistered(
+ ProtocolProviderFactory factory)
+ {
+ queueLoadStoredAccounts(factory);
+ }
+
+ /**
+ * Queues a specific <tt>ProtocolProviderFactory</tt> to have its stored
+ * accounts loaded as soon as possible.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> to be queued for
+ * loading its stored accounts as soon as possible
+ */
+ private void queueLoadStoredAccounts(ProtocolProviderFactory factory)
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ loadStoredAccountsQueue.add(factory);
+ loadStoredAccountsQueue.notifyAll();
+
+ if (loadStoredAccountsThread == null)
+ {
+ loadStoredAccountsThread = new Thread()
+ {
+ @Override
+ public void run()
+ {
+ runInLoadStoredAccountsThread();
+ }
+ };
+ loadStoredAccountsThread.setDaemon(true);
+ loadStoredAccountsThread.setName(
+ "AccountManager.loadStoredAccounts");
+ loadStoredAccountsThread.start();
+ }
+ }
+ }
+
+ /**
+ * Implements AccountManager#removeListener(AccountManagerListener).
+ * @param listener the <tt>AccountManagerListener</tt> to remove
+ */
+ public void removeListener(AccountManagerListener listener)
+ {
+ synchronized (listeners)
+ {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Running in {@link #loadStoredAccountsThread}, loads the stored accounts
+ * of the <tt>ProtocolProviderFactory</tt> services waiting in
+ * {@link #loadStoredAccountsQueue}
+ */
+ private void runInLoadStoredAccountsThread()
+ {
+ boolean interrupted = false;
+ while (!interrupted)
+ {
+ try
+ {
+ ProtocolProviderFactory factory;
+
+ synchronized (loadStoredAccountsQueue)
+ {
+ factory = loadStoredAccountsQueue.poll();
+ if (factory == null)
+ {
+ /*
+ * Technically, we should be handing spurious wakeups.
+ * However, we cannot check the condition in a queue.
+ * Anyway, we just want to keep this Thread alive long
+ * enough to allow it to not be re-created multiple
+ * times and not handing a spurious wakeup will just
+ * cause such an inconvenience.
+ */
+ try
+ {
+ loadStoredAccountsQueue
+ .wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
+ }
+ catch (InterruptedException ex)
+ {
+ logger
+ .warn(
+ "The loading of the stored accounts has"
+ + " been interrupted",
+ ex);
+ interrupted = true;
+ break;
+ }
+ factory = loadStoredAccountsQueue.poll();
+ }
+ if (factory != null)
+ loadStoredAccountsQueue.notifyAll();
+ }
+
+ if (factory != null)
+ {
+ try
+ {
+ loadStoredAccounts(factory);
+ }
+ catch (Exception ex)
+ {
+
+ /*
+ * Swallow the exception in order to prevent a single
+ * factory from halting the loading of subsequent
+ * factories.
+ */
+ logger.error("Failed to load accounts for " + factory,
+ ex);
+ }
+ }
+ }
+ finally
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ if (!interrupted && (loadStoredAccountsQueue.size() <= 0))
+ {
+ if (loadStoredAccountsThread == Thread.currentThread())
+ {
+ loadStoredAccountsThread = null;
+ loadStoredAccountsQueue.notifyAll();
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notifies this manager that an OSGi service has changed. The current
+ * implementation tracks the registrations of
+ * <tt>ProtocolProviderFactory</tt> services in order to queue them for
+ * loading their stored accounts.
+ *
+ * @param serviceEvent the <tt>ServiceEvent</tt> containing the event
+ * data
+ */
+ private void serviceChanged(ServiceEvent serviceEvent)
+ {
+ switch (serviceEvent.getType())
+ {
+ case ServiceEvent.REGISTERED:
+ Object service
+ = bundleContext.getService(serviceEvent.getServiceReference());
+
+ if (service instanceof ProtocolProviderFactory)
+ {
+ protocolProviderFactoryRegistered(
+ (ProtocolProviderFactory) service);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Stores an account represented in the form of an <tt>AccountID</tt>
+ * created by a specific <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which created the
+ * account to be stored
+ * @param accountID the account in the form of <tt>AccountID</tt> to be
+ * stored
+ * @throws OperationFailedException if anything goes wrong while storing the
+ * account
+ */
+ public void storeAccount(
+ ProtocolProviderFactory factory,
+ AccountID accountID)
+ throws OperationFailedException
+ {
+ synchronized (storedAccounts)
+ {
+ if (!storedAccounts.contains(accountID))
+ storedAccounts.add(accountID);
+ }
+
+ ConfigurationService configurationService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ String accountNodeName
+ = getAccountNodeName( factory,
+ accountID.getAccountUniqueID() );
+
+ Map<String, Object> configurationProperties
+ = new HashMap<String, Object>();
+
+ // Create a unique node name of the properties node that will contain
+ // this account's properties.
+ if (accountNodeName == null)
+ {
+ accountNodeName
+ = ACCOUNT_UID_PREFIX + Long.toString(System.currentTimeMillis());
+
+ // set a value for the persistent node so that we could later
+ // retrieve it as a property
+ configurationProperties.put(
+ factoryPackage /* prefix */ + "." + accountNodeName,
+ accountNodeName);
+
+ // register the account in the configuration service.
+ // we register all the properties in the following hierarchy
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ configurationProperties.put(factoryPackage// prefix
+ + "." + accountNodeName // node name for the account id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID, // propname
+ accountID.getAccountUniqueID()); // value
+ }
+
+ // store the rest of the properties
+ Map<String, String> accountProperties = accountID.getAccountProperties();
+
+ for (Map.Entry<String, String> entry : accountProperties.entrySet())
+ {
+ String property = entry.getKey();
+ String value = entry.getValue();
+ String secureStorePrefix = null;
+
+ // If the property is a password, store it securely.
+ if (property.equals(ProtocolProviderFactory.PASSWORD))
+ {
+ String accountPrefix = factoryPackage + "." + accountNodeName;
+ secureStorePrefix = accountPrefix;
+ }
+ else if(property.endsWith("." + ProtocolProviderFactory.PASSWORD))
+ {
+ secureStorePrefix = factoryPackage + "." + accountNodeName +
+ "." + property.substring(0, property.lastIndexOf("."));
+ }
+
+ if(secureStorePrefix != null)
+ {
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+
+ // encrypt and store
+ if ((value != null)
+ && (value.length() != 0)
+ && !credentialsStorage.storePassword(
+ secureStorePrefix,
+ value))
+ {
+ throw
+ new OperationFailedException(
+ "CredentialsStorageService failed to"
+ + " storePassword",
+ OperationFailedException.GENERAL_ERROR);
+ }
+ }
+ else
+ {
+ configurationProperties.put(
+ factoryPackage // prefix
+ + "." + accountNodeName // a unique node name for the account id
+ + "." + property, // propname
+ value); // value
+ }
+ }
+
+ // clear the password if missing property, modification can request
+ // password delete
+ if(!accountProperties.containsKey(ProtocolProviderFactory.PASSWORD)
+ && // And only if it's not stored already in encrypted form.
+ // Account registration object clears also this property
+ // in order to forget the password
+ !configurationProperties.containsKey(
+ factoryPackage+"."+accountNodeName+".ENCRYPTED_PASSWORD"))
+ {
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+ credentialsStorage.removePassword(
+ factoryPackage + "." + accountNodeName);
+ }
+
+ if (configurationProperties.size() > 0)
+ configurationService.setProperties(configurationProperties);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Stored account for id " + accountID.getAccountUniqueID()
+ + " for package " + factoryPackage);
+ }
+
+ /**
+ * Gets account node name under which account configuration properties are
+ * stored.
+ *
+ * @param factory account's protocol provider factory
+ * @param accountUID account for which the prefix will be returned
+ * @return configuration prefix for given <tt>accountID</tt> if exists or
+ * <tt>null</tt> otherwise
+ */
+ public String getAccountNodeName( ProtocolProviderFactory factory,
+ String accountUID )
+ {
+ ConfigurationService configurationService
+ = ProtocolProviderActivator.getConfigurationService();
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ // First check if such accountID already exists in the configuration.
+ List<String> storedAccounts =
+ configurationService.getPropertyNamesByPrefix(factoryPackage, true);
+ String accountNodeName = null;
+
+ for (Iterator<String> storedAccountIter = storedAccounts.iterator();
+ storedAccountIter.hasNext();)
+ {
+ String storedAccount = storedAccountIter.next();
+
+ // If the property is not related to an account we skip it.
+ int dotIndex = storedAccount.lastIndexOf(".");
+ if (!storedAccount.substring(dotIndex + 1)
+ .startsWith(ACCOUNT_UID_PREFIX))
+ continue;
+
+ String storedAccountUID
+ = configurationService.getString(
+ storedAccount + "." + ProtocolProviderFactory.ACCOUNT_UID);
+
+ if(storedAccountUID == null)
+ continue;
+
+ if (storedAccountUID.equals(accountUID))
+ accountNodeName = configurationService.getString(storedAccount);
+ }
+ return accountNodeName;
+ }
+
+ /**
+ * Removes the account with <tt>accountID</tt> from the set of accounts
+ * that are persistently stored inside the configuration service.
+ *
+ * @param factory the <tt>ProtocolProviderFactory</tt> which created the
+ * account to be stored
+ * @param accountID the AccountID of the account to remove.
+ * @return true if an account has been removed and false otherwise.
+ */
+ public boolean removeStoredAccount(ProtocolProviderFactory factory,
+ AccountID accountID)
+ {
+ synchronized (storedAccounts)
+ {
+ if (storedAccounts.contains(accountID))
+ storedAccounts.remove(accountID);
+ }
+
+ /*
+ * We're already doing it in #unloadAccount(AccountID) - we're figuring
+ * out the ProtocolProviderFactory by the AccountID.
+ */
+ if (factory == null)
+ {
+ factory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+ }
+
+ String factoryPackage = getFactoryImplPackageName(factory);
+
+ // remove the stored password explicitly using credentials service
+ CredentialsStorageService credentialsStorage
+ = ServiceUtils.getService(
+ bundleContext,
+ CredentialsStorageService.class);
+ String accountPrefix =
+ ProtocolProviderFactory.findAccountPrefix(bundleContext, accountID,
+ factoryPackage);
+
+ credentialsStorage.removePassword(accountPrefix);
+
+ ConfigurationService configurationService
+ = ServiceUtils.getService(
+ bundleContext,
+ ConfigurationService.class);
+ //first retrieve all accounts that we've registered
+ List<String> storedAccounts
+ = configurationService.getPropertyNamesByPrefix(
+ factoryPackage, true);
+
+ //find an account with the corresponding id.
+ for (String accountRootPropertyName : storedAccounts)
+ {
+ //unregister the account in the configuration service.
+ //all the properties must have been registered in the following
+ //hierarchy:
+ //net.java.sip.communicator.impl.protocol.PROTO_NAME.ACC_ID.PROP_NAME
+ String accountUID = configurationService.getString(
+ accountRootPropertyName //node id
+ + "." + ProtocolProviderFactory.ACCOUNT_UID); // propname
+
+ if (accountID.getAccountUniqueID().equals(accountUID))
+ {
+ //retrieve the names of all properties registered for the
+ //current account.
+ List<String> accountPropertyNames
+ = configurationService.getPropertyNamesByPrefix(
+ accountRootPropertyName, false);
+
+ //set all account properties to null in order to remove them.
+ for (String propName : accountPropertyNames)
+ configurationService.setProperty(propName, null);
+
+ //and now remove the parent too.
+ configurationService.setProperty(accountRootPropertyName, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all accounts which have been persistently stored.
+ *
+ * @see #removeStoredAccount(ProtocolProviderFactory, AccountID)
+ */
+ public void removeStoredAccounts()
+ {
+ synchronized (loadStoredAccountsQueue)
+ {
+ /*
+ * Wait for the Thread which loads the stored account to complete so
+ * that we can be sure later on that it will not load a stored
+ * account while we are deleting it or another one for that matter.
+ */
+ boolean interrupted = false;
+
+ while (loadStoredAccountsThread != null)
+ try
+ {
+ loadStoredAccountsQueue.wait(LOAD_STORED_ACCOUNTS_TIMEOUT);
+ }
+ catch (InterruptedException ie)
+ {
+ interrupted = true;
+ }
+ if (interrupted)
+ Thread.currentThread().interrupt();
+
+ synchronized (this.storedAccounts)
+ {
+ AccountID[] storedAccounts
+ = this.storedAccounts.toArray(
+ new AccountID[this.storedAccounts.size()]);
+
+ for (AccountID storedAccount : storedAccounts)
+ {
+ ProtocolProviderFactory ppf
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ storedAccount.getProtocolName());
+
+ if (ppf != null)
+ ppf.uninstallAccount(storedAccount);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns an <tt>Iterator</tt> over a list of all stored
+ * <tt>AccountID</tt>s. The list of stored accounts include all registered
+ * accounts and all disabled accounts. In other words in this list we could
+ * find accounts that aren't loaded.
+ * <p>
+ * In order to check if an account is already loaded please use the
+ * #isAccountLoaded(AccountID accountID) method. To load an account use the
+ * #loadAccount(AccountID accountID) method.
+ *
+ * @return an <tt>Iterator</tt> over a list of all stored
+ * <tt>AccountID</tt>s
+ */
+ public Collection<AccountID> getStoredAccounts()
+ {
+ synchronized (storedAccounts)
+ {
+ return new Vector<AccountID>(storedAccounts);
+ }
+ }
+
+ /**
+ * Loads the account corresponding to the given <tt>AccountID</tt>. An
+ * account is loaded when its <tt>ProtocolProviderService</tt> is registered
+ * in the bundle context. This method is meant to load the account through
+ * the corresponding <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param accountID the identifier of the account to load
+ * @throws OperationFailedException if anything goes wrong while loading the
+ * account corresponding to the specified <tt>accountID</tt>
+ */
+ public void loadAccount(AccountID accountID)
+ throws OperationFailedException
+ {
+ // If the account with the given id is already loaded we have nothing
+ // to do here.
+ if (isAccountLoaded(accountID))
+ return;
+
+ ProtocolProviderFactory providerFactory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+
+ if(providerFactory.loadAccount(accountID))
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(false));
+ // Finally store the modified properties.
+ storeAccount(providerFactory, accountID);
+ }
+ }
+
+ /**
+ * Unloads the account corresponding to the given <tt>AccountID</tt>. An
+ * account is unloaded when its <tt>ProtocolProviderService</tt> is
+ * unregistered in the bundle context. This method is meant to unload the
+ * account through the corresponding <tt>ProtocolProviderFactory</tt>.
+ *
+ * @param accountID the identifier of the account to load
+ * @throws OperationFailedException if anything goes wrong while unloading
+ * the account corresponding to the specified <tt>accountID</tt>
+ */
+ public void unloadAccount(AccountID accountID)
+ throws OperationFailedException
+ {
+ // If the account with the given id is already unloaded we have nothing
+ // to do here.
+ if (!isAccountLoaded(accountID))
+ return;
+
+ ProtocolProviderFactory providerFactory
+ = ProtocolProviderActivator.getProtocolProviderFactory(
+ accountID.getProtocolName());
+
+ // Obtain the protocol provider.
+ ServiceReference<ProtocolProviderService> serRef
+ = providerFactory.getProviderForAccount(accountID);
+
+ // If there's no such provider we have nothing to do here.
+ if (serRef == null)
+ return;
+
+ ProtocolProviderService protocolProvider
+ = bundleContext.getService(serRef);
+
+ // Set the account icon path for unloaded accounts.
+ String iconPathProperty = accountID.getAccountPropertyString(
+ ProtocolProviderFactory.ACCOUNT_ICON_PATH);
+
+ if (iconPathProperty == null)
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.ACCOUNT_ICON_PATH,
+ protocolProvider.getProtocolIcon()
+ .getIconPath(ProtocolIcon.ICON_SIZE_32x32));
+ }
+
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(true));
+
+ if (!providerFactory.unloadAccount(accountID))
+ {
+ accountID.putAccountProperty(
+ ProtocolProviderFactory.IS_ACCOUNT_DISABLED,
+ String.valueOf(false));
+ }
+ // Finally store the modified properties.
+ storeAccount(providerFactory, accountID);
+ }
+
+ /**
+ * Checks if the account corresponding to the given <tt>accountID</tt> is
+ * loaded. An account is loaded if its <tt>ProtocolProviderService</tt> is
+ * registered in the bundle context. By default all accounts are loaded.
+ * However the user could manually unload an account, which would be
+ * unregistered from the bundle context, but would remain in the
+ * configuration file.
+ *
+ * @param accountID the identifier of the account to load
+ * @return <tt>true</tt> to indicate that the account with the given
+ * <tt>accountID</tt> is loaded, <tt>false</tt> - otherwise
+ */
+ public boolean isAccountLoaded(AccountID accountID)
+ {
+ return storedAccounts.contains(accountID) && accountID.isEnabled();
+ }
+
+ private String stripPackagePrefix(String property)
+ {
+ int packageEndIndex = property.lastIndexOf('.');
+
+ if (packageEndIndex != -1)
+ property = property.substring(packageEndIndex + 1);
+ return property;
+ }
+}