/*
* 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;
import java.util.*;
import org.osgi.framework.*;
import net.java.sip.communicator.service.configuration.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* Represents an implementation of AccountManager
which loads the
* accounts in a separate thread.
*
* @author Lubomir Marinov
*/
public class AccountManagerImpl
implements AccountManager
{
/**
* The delay in milliseconds the background Thread
loading the
* stored accounts should wait before dying so that it doesn't get recreated
* for each ProtocolProviderFactory
registration.
*/
private static final long LOAD_STORED_ACCOUNTS_TIMEOUT = 30000;
/**
* The BundleContext
this service is registered in.
*/
private final BundleContext bundleContext;
/**
* The AccountManagerListener
s currently interested in the
* events fired by this manager.
*/
private final List listeners =
new LinkedList();
/**
* The queue of ProtocolProviderFactory
services awaiting their
* stored accounts to be loaded.
*/
private final Queue loadStoredAccountsQueue =
new LinkedList();
/**
* The Thread
loading the stored accounts of the
* ProtocolProviderFactory
services waiting in
* {@link #loadStoredAccountsQueue}.
*/
private Thread loadStoredAccountsThread;
private final Logger logger = Logger.getLogger(AccountManagerImpl.class);
/**
* Initializes a new AccountManagerImpl
instance loaded in a
* specific BundleContext
(in which the caller will usually
* later register it).
*
* @param bundleContext the BundleContext
in which the new
* instance is loaded (and in which the caller will usually later
* register it as a service)
*/
public AccountManagerImpl(BundleContext bundleContext)
{
this.bundleContext = bundleContext;
this.bundleContext.addServiceListener(new ServiceListener()
{
public void serviceChanged(ServiceEvent serviceEvent)
{
AccountManagerImpl.this.serviceChanged(serviceEvent);
}
});
}
/*
* Implements AccountManager#addListener(AccountManagerListener).
*/
public void addListener(AccountManagerListener listener)
{
synchronized (listeners)
{
if (!listeners.contains(listener))
listeners.add(listener);
}
}
/**
* Loads the accounts stored for a specific
* ProtocolProviderFactory
.
*
* @param factory the ProtocolProviderFactory
to load the
* stored accounts of
*/
private void doLoadStoredAccounts(ProtocolProviderFactory factory)
{
ConfigurationService configService
= ProtocolProviderActivator.getConfigurationService();
String factoryPackage = getFactoryImplPackageName(factory);
List storedAccounts =
configService.getPropertyNamesByPrefix(factoryPackage, true);
if (logger.isDebugEnabled())
logger.debug("Discovered " + storedAccounts.size() + " stored "
+ factoryPackage + " accounts");
for (Iterator storedAccountIter = storedAccounts.iterator(); storedAccountIter
.hasNext();)
{
String storedAccount = storedAccountIter.next();
if (logger.isDebugEnabled())
logger.debug("Loading account " + storedAccount);
List storedAccountProperties =
configService.getPropertyNamesByPrefix(storedAccount, true);
Map accountProperties =
new Hashtable();
for (Iterator storedAccountPropertyIter =
storedAccountProperties.iterator(); storedAccountPropertyIter
.hasNext();)
{
String property = storedAccountPropertyIter.next();
String value = configService.getString(property);
property = stripPackagePrefix(property);
// Decode passwords.
if (ProtocolProviderFactory.PASSWORD.equals(property))
{
if (value == null)
{
value = "";
}
else if (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
{
factory.loadAccount(accountProperties);
}
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);
}
}
}
/**
* Notifies the registered {@link #listeners} that the stored accounts of a
* specific ProtocolProviderFactory
have just been loaded.
*
* @param factory the ProtocolProviderFactory
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);
}
}
}
private String getFactoryImplPackageName(ProtocolProviderFactory factory) {
String className = factory.getClass().getName();
return className.substring(0, className.lastIndexOf('.'));
}
public boolean hasStoredAccounts(String protocolName, boolean includeHidden)
{
ServiceReference[] factoryRefs = null;
boolean hasStoredAccounts = false;
try
{
factoryRefs =
bundleContext.getServiceReferences(
ProtocolProviderFactory.class.getName(), null);
}
catch (InvalidSyntaxException ex)
{
logger.error(
"Failed to retrieve the registered ProtocolProviderFactories",
ex);
}
if ((factoryRefs != null) && (factoryRefs.length > 0))
{
ConfigurationService configService
= ProtocolProviderActivator.getConfigurationService();
for (ServiceReference factoryRef : factoryRefs)
{
ProtocolProviderFactory factory
= (ProtocolProviderFactory)
bundleContext.getService(factoryRef);
if ((protocolName != null)
&& !protocolName.equals(factory.getProtocolName()))
{
continue;
}
String factoryPackage = getFactoryImplPackageName(factory);
List storedAccounts =
configService
.getPropertyNamesByPrefix(factoryPackage, true);
/* Ignore the hidden accounts. */
for (Iterator storedAccountIter =
storedAccounts.iterator(); storedAccountIter.hasNext();)
{
String storedAccount = storedAccountIter.next();
List storedAccountProperties =
configService.getPropertyNamesByPrefix(storedAccount,
true);
boolean hidden = false;
if (!includeHidden)
{
for (Iterator 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);
break;
}
}
}
if (!hidden)
{
hasStoredAccounts = true;
break;
}
}
if (hasStoredAccounts || (protocolName != null))
{
break;
}
}
}
return hasStoredAccounts;
}
/**
* Loads the accounts stored for a specific
* ProtocolProviderFactory
and notifies the registered
* {@link #listeners} that the stored accounts of the specified
* factory
have just been loaded
*
* @param factory the ProtocolProviderFactory
to load the
* stored accounts of
*/
private void loadStoredAccounts(ProtocolProviderFactory factory)
{
doLoadStoredAccounts(factory);
fireStoredAccountsLoaded(factory);
}
/**
* Notifies this manager that a specific
* ProtocolProviderFactory
has been registered as a service.
* The current implementation queues the specified factory
to
* have its stored accounts as soon as possible.
*
* @param factory the ProtocolProviderFactory
which has been
* registered as a service.
*/
private void protocolProviderFactoryRegistered(
ProtocolProviderFactory factory)
{
queueLoadStoredAccounts(factory);
}
/**
* Queues a specific ProtocolProviderFactory
to have its stored
* accounts loaded as soon as possible.
*
* @param factory the ProtocolProviderFactory
to be queued for
* loading its stored accounts as soon as possible
*/
private void queueLoadStoredAccounts(ProtocolProviderFactory factory)
{
synchronized (loadStoredAccountsQueue)
{
loadStoredAccountsQueue.add(factory);
loadStoredAccountsQueue.notify();
if (loadStoredAccountsThread == null)
{
loadStoredAccountsThread = new Thread()
{
public void run()
{
runInLoadStoredAccountsThread();
}
};
loadStoredAccountsThread.setDaemon(true);
loadStoredAccountsThread
.setName("AccountManager.loadStoredAccounts");
loadStoredAccountsThread.start();
}
}
}
/*
* Implements AccountManager#removeListener(AccountManagerListener).
*/
public void removeListener(AccountManagerListener listener)
{
synchronized (listeners)
{
listeners.remove(listener);
}
}
/**
* Running in {@link #loadStoredAccountsThread}, loads the stored accounts
* of the ProtocolProviderFactory
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)
{
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;
}
break;
}
}
}
}
}
/**
* Notifies this manager that an OSGi service has changed. The current
* implementation tracks the registrations of
* ProtocolProviderFactory
services in order to queue them for
* loading their stored accounts.
*
* @param serviceEvent the ServiceEvent
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 AccountID
* created by a specific ProtocolProviderFactory
.
*
* @param factory the ProtocolProviderFactory
which created the
* account to be stored
* @param accountID the account in the form of AccountID
to be
* stored
*/
public void storeAccount(ProtocolProviderFactory factory,
AccountID accountID)
{
ConfigurationService configurationService
= ProtocolProviderActivator.getConfigurationService();
String factoryPackage = getFactoryImplPackageName(factory);
// First check if such accountID already exists in the configuration.
List storedAccounts =
configurationService.getPropertyNamesByPrefix(factoryPackage, true);
String accountUID = accountID.getAccountUniqueID();
String accountNodeName = null;
for (Iterator storedAccountIter = storedAccounts.iterator();
storedAccountIter.hasNext();)
{
String storedAccount = storedAccountIter.next();
String storedAccountUID =
configurationService.getString(storedAccount + ".ACCOUNT_UID");
if (storedAccountUID.equals(accountUID))
{
accountNodeName = configurationService.getString(storedAccount);
}
}
Map configurationProperties
= new HashMap();
// Create a unique node name of the properties node that will contain
// this account's properties.
if (accountNodeName == null)
{
accountNodeName = "acc" + 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 accountProperties = accountID.getAccountProperties();
for (Map.Entry entry : accountProperties.entrySet())
{
String property = entry.getKey();
String value = entry.getValue();
// if this is a password - encode it.
if (property.equals(ProtocolProviderFactory.PASSWORD))
value = new String(Base64.encode(value.getBytes()));
configurationProperties.put(factoryPackage // prefix
+ "." + accountNodeName // a unique node name for the account id
+ "." + property, // propname
value); // value
}
if (configurationProperties.size() > 0)
configurationService.setProperties(configurationProperties);
if (logger.isDebugEnabled())
logger.debug("Stored account for id " + accountID.getAccountUniqueID()
+ " for package " + factoryPackage);
}
private String stripPackagePrefix(String property)
{
int packageEndIndex = property.lastIndexOf('.');
if (packageEndIndex != -1)
{
property = property.substring(packageEndIndex + 1);
}
return property;
}
}