/* * 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.service.protocol; import java.util.*; import org.osgi.framework.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.util.*; /** * The ProtocolProviderFactory is what actually creates instances of a * ProtocolProviderService implementation. A provider factory would register, * persistently store, and remove when necessary, ProtocolProviders. The way * things are in the SIP Communicator, a user account is represented (in a 1:1 * relationship) by an AccountID and a ProtocolProvider. In other words - one * would have as many protocol providers installed in a given moment as they * would user account registered through the various services. * * @author Emil Ivov * @author Lubomir Marinov */ public abstract class ProtocolProviderFactory { private static final Logger logger = Logger.getLogger(ProtocolProviderFactory.class); /** * Then name of a property which represents a password. */ public static final String PASSWORD = "PASSWORD"; /** * The name of a property representing the name of the protocol for an * ProtocolProviderFactory. */ public static final String PROTOCOL = "PROTOCOL_NAME"; /** * The name of a property representing the path to protocol icons. */ public static final String PROTOCOL_ICON_PATH = "PROTOCOL_ICON_PATH"; /** * The name of a property which represents the AccountID of a * ProtocolProvider and that, together with a password is used to login * on the protocol network.. */ public static final String USER_ID = "USER_ID"; /** * The name that should be displayed to others when we are calling or * writing them. */ public static final String DISPLAY_NAME = "DISPLAY_NAME"; /** * The name of the property under which we store protocol AccountID-s. */ public static final String ACCOUNT_UID = "ACCOUNT_UID"; /** * The name of the property under which we store protocol the address of * a protocol centric entity (any protocol server). */ public static final String SERVER_ADDRESS = "SERVER_ADDRESS"; /** * The name of the property under which we store the number of the port * where the server stored against the SERVER_ADDRESS property is expecting * connections to be made via this protocol. */ public static final String SERVER_PORT = "SERVER_PORT"; /** * The name of the property under which we store the name of the transport * protocol that needs to be used to access the server. */ public static final String SERVER_TRANSPORT = "SERVER_TRANSPORT"; /** * The name of the property under which we store protocol the address of * a protocol proxy. */ public static final String PROXY_ADDRESS = "PROXY_ADDRESS"; /** * The name of the property under which we store the number of the port * where the proxy stored against the PROXY_ADDRESS property is expecting * connections to be made via this protocol. */ public static final String PROXY_PORT = "PROXY_PORT"; /** * The name of the property under which we store the the type of the proxy * stored against the PROXY_ADDRESS property. Exact type values depend on * protocols and among them are socks4, socks5, http and possibly others. */ public static final String PROXY_TYPE = "PROXY_TYPE"; /** * The name of the property under which we store the the username for the proxy * stored against the PROXY_ADDRESS property. */ public static final String PROXY_USERNAME = "PROXY_USERNAME"; /** * The name of the property under which we store the password for the proxy * stored against the PROXY_ADDRESS property. */ public static final String PROXY_PASSWORD = "PROXY_PASSWORD"; /** * The name of the property under which we store the name of the transport * protocol that needs to be used to access the proxy. */ public static final String PROXY_TRANSPORT = "PROXY_TRANSPORT"; /** * The name of the property under which we store the user preference for a * transport protocol to use (i.e. tcp or udp). */ public static final String PREFERRED_TRANSPORT = "PREFERRED_TRANSPORT"; /** * The name of the property under which we store resources such as the jabber * resource property. */ public static final String RESOURCE = "RESOURCE"; /** * The name of the property under which we store resource priority. */ public static final String RESOURCE_PRIORITY = "RESOURCE_PRIORITY"; /** * The name of the property which defines that the call is encrypted by default */ public static final String DEFAULT_ENCRYPTION = "DEFAULT_ENCRYPTION"; /** * The name of the property under which we store the boolean value * indicating if the user name should be automatically changed if the * specified name already exists. This property is meant to be used by IRC * implementations. */ public static final String AUTO_CHANGE_USER_NAME = "AUTO_CHANGE_USER_NAME"; /** * The name of the property under which we store the boolean value * indicating if a password is required. Initially this property is meant to * be used by IRC implementations. */ public static final String NO_PASSWORD_REQUIRED = "NO_PASSWORD_REQUIRED"; /** * The name of the property under which we store if the presence is enabled. */ public static final String IS_PRESENCE_ENABLED = "IS_PRESENCE_ENABLED"; /** * The name of the property under which we store if the p2p mode for SIMPLE * should be forced. */ public static final String FORCE_P2P_MODE = "FORCE_P2P_MODE"; /** * The name of the property under which we store the offline contact polling * period for SIMPLE. */ public static final String POLLING_PERIOD = "POLLING_PERIOD"; /** * The name of the property under which we store the chosen default * subscription expiration value for SIMPLE. */ public static final String SUBSCRIPTION_EXPIRATION = "SUBSCRIPTION_EXPIRATION"; /** * Indicates if the server address has been validated. */ public static final String SERVER_ADDRESS_VALIDATED = "SERVER_ADDRESS_VALIDATED"; /** * Indicates if the server settings are over */ public static final String IS_SERVER_OVERRIDDEN = "IS_SERVER_OVERRIDDEN"; /** * Indicates if the proxy address has been validated. */ public static final String PROXY_ADDRESS_VALIDATED = "PROXY_ADDRESS_VALIDATED"; /** * Indicates the search strategy chosen for the DICT protocole. */ public static final String STRATEGY = "STRATEGY"; /** * Indicates a protocol that would not be shown in the user interface as an * account. */ public static final String IS_PROTOCOL_HIDDEN = "IS_PROTOCOL_HIDDEN"; /** * The <code>BundleContext</code> containing (or to contain) the service * registration of this factory. */ private final BundleContext bundleContext; /** * The name of the protocol this factory registers its * <code>ProtocolProviderService</code>s with and to be placed in the * properties of the accounts created by this factory. */ private final String protocolName; /** * The table that we store our accounts in. * <p> * TODO Synchronize the access to the field which may in turn be better * achieved by also hiding it from protected into private access. * </p> */ protected final Hashtable<AccountID, ServiceRegistration> registeredAccounts = new Hashtable<AccountID, ServiceRegistration>(); protected ProtocolProviderFactory(BundleContext bundleContext, String protocolName) { this.bundleContext = bundleContext; this.protocolName = protocolName; } /** * Gets the <code>BundleContext</code> containing (or to contain) the * service registration of this factory. * * @return the <code>BundleContext</code> containing (or to contain) the * service registration of this factory */ public BundleContext getBundleContext() { return bundleContext; } /** * Initializes and creates an account corresponding to the specified * accountProperties and registers the resulting ProtocolProvider in the * <tt>context</tt> BundleContext parameter. Note that account * registration is persistent and accounts that are registered during * a particular sip-communicator session would be automatically reloaded * during all following sessions until they are removed through the * removeAccount method. * * @param userID the user identifier uniquely representing the newly * created account within the protocol namespace. * @param accountProperties a set of protocol (or implementation) specific * properties defining the new account. * @return the AccountID of the newly created account. * @throws java.lang.IllegalArgumentException if userID does not correspond * to an identifier in the context of the underlying protocol or if * accountProperties does not contain a complete set of account installation * properties. * @throws java.lang.IllegalStateException if the account has already been * installed. * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract AccountID installAccount(String userID, Map<String, String> accountProperties) throws IllegalArgumentException, IllegalStateException, NullPointerException; /** * Modifies the account corresponding to the specified accountID. This * method is meant to be used to change properties of already existing * accounts. Note that if the given accountID doesn't correspond to any * registered account this method would do nothing. * * @param protocolProvider the protocol provider service corresponding to * the modified account. * @param accountProperties a set of protocol (or implementation) specific * properties defining the new account. * * @throws java.lang.NullPointerException if any of the arguments is null. */ public abstract void modifyAccount( ProtocolProviderService protocolProvider, Map<String, String> accountProperties) throws NullPointerException; /** * Returns a copy of the list containing the <tt>AccountID</tt>s of all * accounts currently registered in this protocol provider. * @return a copy of the list containing the <tt>AccountID</tt>s of all * accounts currently registered in this protocol provider. */ public ArrayList<AccountID> getRegisteredAccounts() { synchronized (registeredAccounts) { return new ArrayList<AccountID>(registeredAccounts.keySet()); } } /** * Returns the ServiceReference for the protocol provider corresponding to * the specified accountID or null if the accountID is unknown. * @param accountID the accountID of the protocol provider we'd like to get * @return a ServiceReference object to the protocol provider with the * specified account id and null if the account id is unknown to the * provider factory. */ public ServiceReference getProviderForAccount(AccountID accountID) { ServiceRegistration registration; synchronized (registeredAccounts) { registration = (ServiceRegistration) registeredAccounts.get(accountID); } return (registration == null) ? null : registration.getReference(); } /** * Removes the specified account from the list of accounts that this * provider factory is handling. If the specified accountID is unknown to the * ProtocolProviderFactory, the call has no effect and false is returned. * This method is persistent in nature and once called the account * corresponding to the specified ID will not be loaded during future runs * of the project. * * @param accountID the ID of the account to remove. * @return true if an account with the specified ID existed and was removed * and false otherwise. */ public boolean uninstallAccount(AccountID accountID) { // Unregister the protocol provider. ServiceReference serRef = getProviderForAccount(accountID); if (serRef == null) { return false; } BundleContext bundleContext = getBundleContext(); ProtocolProviderService protocolProvider = (ProtocolProviderService) bundleContext.getService(serRef); try { protocolProvider.unregister(); } catch (OperationFailedException ex) { logger .error("Failed to unregister protocol provider for account : " + accountID + " caused by: " + ex); } ServiceRegistration registration; synchronized (registeredAccounts) { registration = registeredAccounts.remove(accountID); } if (registration == null) { return false; } // Kill the service. registration.unregister(); return removeStoredAccount(bundleContext, accountID); } /** * The method stores the specified account in the configuration service * under the package name of the source factory. The restore and remove * account methods are to be used to obtain access to and control the stored * accounts. * <p> * In order to store all account properties, the method would create an * entry in the configuration service corresponding (beginning with) the * <tt>sourceFactory</tt>'s package name and add to it a unique identifier * (e.g. the current miliseconds.) * </p> * * @param accountID the AccountID corresponding to the account that we would * like to store. */ protected void storeAccount(AccountID accountID) { getAccountManager().storeAccount(this, accountID); } /** * Saves the password for the specified account after scrambling it a bit so * that it is not visible from first sight. (The method remains highly * insecure). * * @param accountID the AccountID for the account whose password we're * storing * @param password the password itself * * @throws IllegalArgumentException if no account corresponding to * <code>accountID</code> has been previously stored */ public void storePassword(AccountID accountID, String password) throws IllegalArgumentException { storePassword(getBundleContext(), accountID, password); } /** * Saves the password for the specified account after scrambling it a bit * so that it is not visible from first sight (Method remains highly * insecure). * <p> * TODO Delegate the implementation to {@link AccountManager} because it * knows the format in which the password (among the other account * properties) is to be saved. * </p> * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID for the account whose password we're * storing. * @param password the password itself. * * @throws IllegalArgumentException if no account corresponding to * <tt>accountID</tt> has been previously stored. */ protected void storePassword(BundleContext bundleContext, AccountID accountID, String password) throws IllegalArgumentException { String accountPrefix = findAccountPrefix( bundleContext, accountID); if (accountPrefix == null) throw new IllegalArgumentException( "No previous records found for account ID: " + accountID.getAccountUniqueID() + " in package" + getFactoryImplPackageName()); //obscure the password String mangledPassword = null; //if password is null then the caller simply wants the current password //removed from the cache. make sure they don't get a null pointer //instead. if(password != null) mangledPassword = new String(Base64.encode(password.getBytes())); //get a reference to the config service and store it. ServiceReference confReference = bundleContext.getServiceReference( ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); configurationService.setProperty( accountPrefix + "." + PASSWORD, mangledPassword); } /** * Returns the password last saved for the specified account. * * @param accountID the AccountID for the account whose password we're * looking for * * @return a String containing the password for the specified accountID */ public String loadPassword(AccountID accountID) { return loadPassword(getBundleContext(), accountID); } /** * Returns the password last saved for the specified account. * <p> * TODO Delegate the implementation to {@link AccountManager} because it * knows the format in which the password (among the other account * properties) was saved. * </p> * * @param bundleContext a currently valid bundle context. * @param accountID the AccountID for the account whose password we're * looking for.. * * @return a String containing the password for the specified accountID. */ protected String loadPassword(BundleContext bundleContext, AccountID accountID) { String accountPrefix = findAccountPrefix( bundleContext, accountID); if (accountPrefix == null) return null; //get a reference to the config service and store it. ServiceReference confReference = bundleContext.getServiceReference( ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); //obscure the password String mangledPassword = configurationService.getString( accountPrefix + "." + PASSWORD); if(mangledPassword == null) return null; return new String(Base64.decode(mangledPassword)); } /** * Initializes and creates an account corresponding to the specified * accountProperties and registers the resulting ProtocolProvider in the * <tt>context</tt> BundleContext parameter. This method has a persistent * effect. Once created the resulting account will remain installed until * removed through the uninstallAccount method. * * @param accountProperties a set of protocol (or implementation) specific * properties defining the new account. * @return the AccountID of the newly loaded account */ public AccountID loadAccount(Map<String, String> accountProperties) { BundleContext bundleContext = getBundleContext(); if (bundleContext == null) throw new NullPointerException( "The specified BundleContext was null"); if (accountProperties == null) throw new NullPointerException( "The specified property map was null"); String userID = accountProperties.get(USER_ID); if (userID == null) throw new NullPointerException( "The account properties contained no user id."); String protocolName = getProtocolName(); if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName); AccountID accountID = createAccountID(userID, accountProperties); ProtocolProviderService service = createService(userID, accountID); Dictionary<String, String> properties = new Hashtable<String, String>(); properties.put(PROTOCOL, protocolName); properties.put(USER_ID, userID); ServiceRegistration serviceRegistration = bundleContext.registerService(ProtocolProviderService.class .getName(), service, properties); synchronized (registeredAccounts) { registeredAccounts.put(accountID, serviceRegistration); } return accountID; } /** * Creates a new <code>AccountID</code> instance with a specific user ID to * represent a given set of account properties. * <p> * The method is a pure factory allowing implementers to specify the runtime * type of the created <code>AccountID</code> and customize the instance. * The returned <code>AccountID</code> will later be associated with a * <code>ProtocolProviderService</code> by the caller (e.g. using * {@link #createService(String, AccountID)}). * </p> * * @param userID the user ID of the new instance * @param accountProperties the set of properties to be represented by the * new instance * @return a new <code>AccountID</code> instance with the specified user ID * representing the given set of account properties */ protected abstract AccountID createAccountID(String userID, Map<String, String> accountProperties); /** * Gets the name of the protocol this factory registers its * <code>ProtocolProviderService</code>s with and to be placed in the * properties of the accounts created by this factory. * * @return the name of the protocol this factory registers its * <code>ProtocolProviderService</code>s with and to be placed in * the properties of the accounts created by this factory */ public String getProtocolName() { return protocolName; } /** * Initializes a new <code>ProtocolProviderService</code> instance with a * specific user ID to represent a specific <code>AccountID</code>. * <p> * The method is a pure factory allowing implementers to specify the runtime * type of the created <code>ProtocolProviderService</code> and customize * the instance. The caller will later register the returned service with * the <code>BundleContext</code> of this factory. * </p> * * @param userID the user ID to initialize the new instance with * @param accountID the <code>AccountID</code> to be represented by the new * instance * @return a new <code>ProtocolProviderService</code> instance with the * specific user ID representing the specified * <code>AccountID</code> */ protected abstract ProtocolProviderService createService(String userID, AccountID accountID); /** * Removes the account with <tt>accountID</tt> from the set of accounts * that are persistently stored inside the configuration service. * <p> * @param bundleContext a currently valid bundle context. * @param accountID the AccountID of the account to remove. * <p> * @return true if an account has been removed and false otherwise. */ protected boolean removeStoredAccount(BundleContext bundleContext, AccountID accountID) { String sourcePackageName = getFactoryImplPackageName(); ServiceReference confReference = bundleContext.getServiceReference( ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); //first retrieve all accounts that we've registered List<String> storedAccounts = configurationService.getPropertyNamesByPrefix( sourcePackageName, 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 + "." + ACCOUNT_UID); // propname if (accountUID.equals(accountID.getAccountUniqueID())) { //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; } /** * Returns the prefix for all persistently stored properties of the account * with the specified id. * @param bundleContext a currently valid bundle context. * @param accountID the AccountID of the account whose properties we're * looking for. * @return a String indicating the ConfigurationService property name * prefix under which all account properties are stored or null if no * account corresponding to the specified id was found. */ protected String findAccountPrefix(BundleContext bundleContext, AccountID accountID) { String sourcePackageName = getFactoryImplPackageName(); ServiceReference confReference = bundleContext.getServiceReference( ConfigurationService.class.getName()); ConfigurationService configurationService = (ConfigurationService) bundleContext.getService(confReference); //first retrieve all accounts that we've registered List<String> storedAccounts = configurationService.getPropertyNamesByPrefix( sourcePackageName, 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 + "." + ACCOUNT_UID); // propname if (accountUID.equals(accountID.getAccountUniqueID())) { return accountRootPropertyName; } } return null; } /** * Returns the name of the package that we're currently running in (i.e. * the name of the package containing the proto factory that extends us). * * @return a String containing the package name of the concrete factory * class that extends us. */ private String getFactoryImplPackageName() { String className = getClass().getName(); return className.substring(0, className.lastIndexOf('.')); } /** * Prepares the factory for bundle shutdown. */ public void stop() { logger.trace("Preparing to stop all protocol providers of" + this); synchronized (registeredAccounts) { for (Enumeration<ServiceRegistration> registrations = registeredAccounts.elements(); registrations.hasMoreElements();) { ServiceRegistration reg = registrations.nextElement(); stop(reg); reg.unregister(); } registeredAccounts.clear(); } } /** * Shuts down the <code>ProtocolProviderService</code> representing an * account registered with this factory. * * @param registeredAccount the <code>ServiceRegistration</code> of the * <code>ProtocolProviderService</code> representing an account * registered with this factory */ protected void stop(ServiceRegistration registeredAccount) { ProtocolProviderService protocolProviderService = (ProtocolProviderService) getBundleContext().getService( registeredAccount.getReference()); protocolProviderService.shutdown(); } private AccountManager getAccountManager() { BundleContext bundleContext = getBundleContext(); ServiceReference serviceReference = bundleContext.getServiceReference(AccountManager.class.getName()); return (AccountManager) bundleContext.getService(serviceReference); } }