/*
* 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.lang.reflect.*;
import java.util.*;
import net.java.sip.communicator.service.credentialsstorage.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.configuration.*;
import org.osgi.framework.*;
/**
* 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
{
/**
* The <tt>Logger</tt> used by the <tt>ProtocolProviderFactory</tt> class
* and its instances for logging output.
*/
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 representing the path to the account icon to
* be used in the user interface, when the protocol provider service is not
* available.
*/
public static final String ACCOUNT_ICON_PATH = "ACCOUNT_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 that should be displayed to the user on call via and chat via
* lists.
*/
public static final String ACCOUNT_DISPLAY_NAME = "ACCOUNT_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 which defines whether proxy is auto configured
* by the protocol by using known methods such as specific DNS queries.
*/
public static final String PROXY_AUTO_CONFIG = "PROXY_AUTO_CONFIG";
/**
* The property indicating the preferred UDP and TCP
* port to bind to for clear communications.
*/
public static final String PREFERRED_CLEAR_PORT_PROPERTY_NAME
= "net.java.sip.communicator.SIP_PREFERRED_CLEAR_PORT";
/**
* The property indicating the preferred TLS (TCP)
* port to bind to for secure communications.
*/
public static final String PREFERRED_SECURE_PORT_PROPERTY_NAME
= "net.java.sip.communicator.SIP_PREFERRED_SECURE_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 the authorization name
* for the proxy stored against the PROXY_ADDRESS property.
*/
public static final String AUTHORIZATION_NAME = "AUTHORIZATION_NAME";
/**
* 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 that indicates whether loose routing should be
* forced for all traffic in an account, rather than routing through an
* outbound proxy which is the default for Jitsi.
*/
public static final String FORCE_PROXY_BYPASS = "FORCE_PROXY_BYPASS";
/**
* The name of the property that indicates whether the client must
* be registered with a registrar when making outgoing calls.
*/
public static final String MUST_REGISTER_TO_CALL = "MUST_REGISTER_TO_CALL";
/**
* 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 whether we generate
* resource values or we just use the stored one.
*/
public static final String AUTO_GENERATE_RESOURCE = "AUTO_GENERATE_RESOURCE";
/**
* 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 that indicates the encryption protocols for this
* account.
*/
public static final String ENCRYPTION_PROTOCOL = "ENCRYPTION_PROTOCOL";
/**
* The name of the property that indicates the status (enabed or disabled)
* encryption protocols for this account.
*/
public static final String ENCRYPTION_PROTOCOL_STATUS
= "ENCRYPTION_PROTOCOL_STATUS";
/**
* The name of the property which defines if to include the ZRTP attribute
* to SIP/SDP
*/
public static final String DEFAULT_SIPZRTP_ATTRIBUTE =
"DEFAULT_SIPZRTP_ATTRIBUTE";
/**
* The name of the property which defines the ID of the client TLS
* certificate configuration entry.
*/
public static final String CLIENT_TLS_CERTIFICATE =
"CLIENT_TLS_CERTIFICATE";
/**
* 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";
/**
* Indicates if the given account is the preferred account.
*/
public static final String IS_PREFERRED_PROTOCOL = "IS_PREFERRED_PROTOCOL";
/**
* The name of the property that would indicate if a given account is
* currently enabled or disabled.
*/
public static final String IS_ACCOUNT_DISABLED = "IS_ACCOUNT_DISABLED";
/**
* The name of the property that would indicate if a given account
* configuration form is currently hidden.
*/
public static final String IS_ACCOUNT_CONFIG_HIDDEN = "IS_CONFIG_HIDDEN";
/**
* The name of the property that would indicate if a given account
* status menu is currently hidden.
*/
public static final String IS_ACCOUNT_STATUS_MENU_HIDDEN =
"IS_STATUS_MENU_HIDDEN";
/**
* The name of the property that would indicate if a given account
* configuration is read only.
*/
public static final String IS_ACCOUNT_READ_ONLY = "IS_READ_ONLY";
/**
* The name of the property that would indicate if a given account
* groups are readonly, values can be all or a comma separated
* group names including root.
*/
public static final String ACCOUNT_READ_ONLY_GROUPS = "READ_ONLY_GROUPS";
/**
* Indicates if ICE should be used.
*/
public static final String IS_USE_ICE = "ICE_ENABLED";
/**
* Indicates if STUN server should be automatically discovered.
*/
public static final String AUTO_DISCOVER_STUN = "AUTO_DISCOVER_STUN";
/**
* Indicates if default STUN server would be used if no other STUN/TURN
* server are available.
*/
public static final String USE_DEFAULT_STUN_SERVER
= "USE_DEFAULT_STUN_SERVER";
/**
* The name of the boolean account property which indicates whether Jitsi
* Videobridge is to be used, if available and supported, for conference
* calls.
*/
public static final String USE_JITSI_VIDEO_BRIDGE
= "USE_JITSI_VIDEO_BRIDGE";
/**
* The name of the boolean account property which indicates whether Jitsi
* will use translator for media, instead of mixing, for conference
* calls.
* By default if supported mixing is used (audio mixed, video relayed).
*/
public static final String USE_TRANSLATOR_IN_CONFERENCE
= "USE_TRANSLATOR_IN_CONFERENCE";
/**
* The property name prefix for all stun server properties. We generally use
* this prefix in conjunction with an index which is how we store multiple
* servers.
*/
public static final String STUN_PREFIX = "STUN";
/**
* The base property name for address of additional STUN servers specified.
*/
public static final String STUN_ADDRESS = "ADDRESS";
/**
* The base property name for port of additional STUN servers specified.
*/
public static final String STUN_PORT = "PORT";
/**
* The base property name for username of additional STUN servers specified.
*/
public static final String STUN_USERNAME = "USERNAME";
/**
* The base property name for password of additional STUN servers specified.
*/
public static final String STUN_PASSWORD = "PASSWORD";
/**
* The base property name for the turn supported property of additional
* STUN servers specified.
*/
public static final String STUN_IS_TURN_SUPPORTED = "IS_TURN_SUPPORTED";
/**
* Indicates if JingleNodes should be used with ICE.
*/
public static final String IS_USE_JINGLE_NODES = "JINGLE_NODES_ENABLED";
/**
* Indicates if JingleNodes should be used with ICE.
*/
public static final String AUTO_DISCOVER_JINGLE_NODES
= "AUTO_DISCOVER_JINGLE_NODES";
/**
* Indicates if JingleNodes should use buddies to search for nodes.
*/
public static final String JINGLE_NODES_SEARCH_BUDDIES
= "JINGLE_NODES_SEARCH_BUDDIES";
/**
* Indicates if UPnP should be used with ICE.
*/
public static final String IS_USE_UPNP = "UPNP_ENABLED";
/**
* Indicates if we allow non-TLS connection.
*/
public static final String IS_ALLOW_NON_SECURE = "ALLOW_NON_SECURE";
/**
* Enable notifications for new voicemail messages.
*/
public static final String VOICEMAIL_ENABLED = "VOICEMAIL_ENABLED";
/**
* Address used to reach voicemail box, by services able to
* subscribe for voicemail new messages notifications.
*/
public static final String VOICEMAIL_URI = "VOICEMAIL_URI";
/**
* Address used to call to hear your messages stored on the server
* for your voicemail.
*/
public static final String VOICEMAIL_CHECK_URI = "VOICEMAIL_CHECK_URI";
/**
* Indicates if calling is disabled for a certain account.
*/
public static final String IS_CALLING_DISABLED_FOR_ACCOUNT
= "CALLING_DISABLED";
/**
* Indicates if desktop streaming/sharing is disabled for a certain account.
*/
public static final String IS_DESKTOP_STREAMING_DISABLED
= "DESKTOP_STREAMING_DISABLED";
/**
* Indicates if desktop remote control is disabled for a certain account.
*/
public static final String IS_DESKTOP_REMOTE_CONTROL_DISABLED
= "DESKTOP_REMOTE_CONTROL_DISABLED";
/**
* The sms default server address.
*/
public static final String SMS_SERVER_ADDRESS = "SMS_SERVER_ADDRESS";
/**
* Keep-alive method used by the protocol.
*/
public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";
/**
* The interval for keep-alives if any.
*/
public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";
/**
* The name of the property holding DTMF method.
*/
public static final String DTMF_METHOD = "DTMF_METHOD";
/**
* The minimal DTMF tone duration.
*/
public static final String DTMF_MINIMAL_TONE_DURATION
= "DTMF_MINIMAL_TONE_DURATION";
/**
* Paranoia mode when turned on requires all calls to be secure and
* indicated as such.
*/
public static final String MODE_PARANOIA = "MODE_PARANOIA";
/**
* The name of the "override encodings" property
*/
public static final String OVERRIDE_ENCODINGS = "OVERRIDE_ENCODINGS";
/**
* The prefix used to store account encoding properties
*/
public static final String ENCODING_PROP_PREFIX = "Encodings";
/**
* An account property to provide a connected account to check for
* its status. Used when the current provider need to reject calls
* but is missing presence operation set and need to check other
* provider for status.
*/
public static final String CUSAX_PROVIDER_ACCOUNT_PROP
= "cusax.XMPP_ACCOUNT_ID";
/**
* 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 Map<AccountID, ServiceRegistration<ProtocolProviderService>>
registeredAccounts
= new HashMap<AccountID, ServiceRegistration<ProtocolProviderService>>();
/**
* The name of the property that indicates the AVP type.
* <ul>
* <li>{@link #SAVP_OFF}</li>
* <li>{@link #SAVP_MANDATORY}</li>
* <li>{@link #SAVP_OPTIONAL}</li>
* </ul>
*/
public static final String SAVP_OPTION = "SAVP_OPTION";
/**
* Always use RTP/AVP
*/
public static final int SAVP_OFF = 0;
/**
* Always use RTP/SAVP
*/
public static final int SAVP_MANDATORY = 1;
/**
* Sends two media description, with RTP/SAVP being first.
*/
public static final int SAVP_OPTIONAL = 2;
/**
* The name of the property that defines the enabled SDES cipher suites.
* Enabled suites are listed as CSV by their RFC name.
*/
public static final String SDES_CIPHER_SUITES = "SDES_CIPHER_SUITES";
/**
* The name of the property that defines the enabled/disabled state of
* message carbons.
*/
public static final String IS_CARBON_DISABLED = "CARBON_DISABLED";
/**
* Creates a new <tt>ProtocolProviderFactory</tt>.
*
* @param bundleContext the bundle context reference of the service
* @param protocolName the name of the protocol
*/
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<ProtocolProviderService> getProviderForAccount(
AccountID accountID)
{
ServiceRegistration<ProtocolProviderService> registration;
synchronized (registeredAccounts)
{
registration = registeredAccounts.get(accountID);
}
try
{
if (registration != null)
return registration.getReference();
}
catch (IllegalStateException ise)
{
synchronized (registeredAccounts)
{
registeredAccounts.remove(accountID);
}
}
return null;
}
/**
* 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<ProtocolProviderService> serRef
= getProviderForAccount(accountID);
boolean wasAccountExisting = false;
// If the protocol provider service is registered, first unregister the
// service.
if (serRef != null)
{
BundleContext bundleContext = getBundleContext();
ProtocolProviderService protocolProvider
= bundleContext.getService(serRef);
try
{
protocolProvider.unregister();
}
catch (OperationFailedException ex)
{
logger.error(
"Failed to unregister protocol provider for account: "
+ accountID + " caused by: " + ex);
}
}
ServiceRegistration<ProtocolProviderService> registration;
synchronized (registeredAccounts)
{
registration = registeredAccounts.remove(accountID);
}
// first remove the stored account so when PP is unregistered we can
// distinguish between deleted or just disabled account
wasAccountExisting = removeStoredAccount(accountID);
if (registration != null)
{
// Kill the service.
registration.unregister();
}
return wasAccountExisting;
}
/**
* 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)
{
this.storeAccount(accountID, true);
}
/**
* 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.
* @param isModification if <tt>false</tt> there must be no such already
* loaded account, it <tt>true</tt> ist modification of an existing account.
* Usually we use this method with <tt>false</tt> in method installAccount
* and with <tt>true</tt> or the overridden method in method
* modifyAccount.
*/
protected void storeAccount(AccountID accountID, boolean isModification)
{
if(!isModification
&& getAccountManager().getStoredAccounts().contains(accountID))
{
throw new IllegalStateException(
"An account for id " + accountID.getUserID()
+ " was already loaded!");
}
try
{
getAccountManager().storeAccount(this, accountID);
}
catch (OperationFailedException ofex)
{
throw new UndeclaredThrowableException(ofex);
}
}
/**
* 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
{
try
{
storePassword(getBundleContext(), accountID, password);
}
catch (OperationFailedException ofex)
{
throw new UndeclaredThrowableException(ofex);
}
}
/**
* 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 <tt>AccountID</tt> of the account whose password is
* to be stored
* @param password the password to be stored
*
* @throws IllegalArgumentException if no account corresponding to
* <tt>accountID</tt> has been previously stored.
* @throws OperationFailedException if anything goes wrong while storing the
* specified <tt>password</tt>
*/
protected void storePassword(BundleContext bundleContext,
AccountID accountID,
String password)
throws IllegalArgumentException,
OperationFailedException
{
String accountPrefix
= findAccountPrefix(
bundleContext,
accountID,
getFactoryImplPackageName());
if (accountPrefix == null)
{
throw
new IllegalArgumentException(
"No previous records found for account ID: "
+ accountID.getAccountUniqueID()
+ " in package"
+ getFactoryImplPackageName());
}
CredentialsStorageService credentialsStorage
= ServiceUtils.getService(
bundleContext,
CredentialsStorageService.class);
if (!credentialsStorage.storePassword(accountPrefix, password))
{
throw
new OperationFailedException(
"CredentialsStorageService failed to storePassword",
OperationFailedException.GENERAL_ERROR);
}
// Update password property also in the AccountID
// to prevent it from being removed during account reload
// in some cases.
accountID.setPassword(password);
}
/**
* 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, getFactoryImplPackageName());
if (accountPrefix == null)
return null;
CredentialsStorageService credentialsStorage
= ServiceUtils.getService(
bundleContext,
CredentialsStorageService.class);
return credentialsStorage.loadPassword(accountPrefix);
}
/**
* 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)
{
AccountID accountID = createAccount(accountProperties);
loadAccount(accountID);
return accountID;
}
/**
* Creates a protocol provider for the given <tt>accountID</tt> and
* registers it in the bundle context. This method has a persistent
* effect. Once created the resulting account will remain installed until
* removed through the uninstallAccount method.
*
* @param accountID the account identifier
* @return <tt>true</tt> if the account with the given <tt>accountID</tt> is
* successfully loaded, otherwise returns <tt>false</tt>
*/
public boolean loadAccount(AccountID accountID)
{
// Need to obtain the original user id property, instead of calling
// accountID.getUserID(), because this method could return a modified
// version of the user id property.
String userID
= accountID.getAccountPropertyString(
ProtocolProviderFactory.USER_ID);
ProtocolProviderService service = createService(userID, accountID);
Dictionary<String, String> properties = new Hashtable<String, String>();
properties.put(PROTOCOL, protocolName);
properties.put(USER_ID, userID);
ServiceRegistration<ProtocolProviderService> serviceRegistration
= bundleContext.registerService(
ProtocolProviderService.class,
service,
properties);
if (serviceRegistration == null)
{
return false;
}
else
{
synchronized (registeredAccounts)
{
registeredAccounts.put(accountID, serviceRegistration);
}
return true;
}
}
/**
* Unloads the account corresponding to the given <tt>accountID</tt>.
* Unregisters the corresponding protocol provider, but keeps the account in
* contrast to the uninstallAccount method.
*
* @param accountID the account identifier
* @return true if an account with the specified ID existed and was unloaded
* and false otherwise.
*/
public boolean unloadAccount(AccountID accountID)
{
// Unregister the protocol provider.
ServiceReference<ProtocolProviderService> serRef
= getProviderForAccount(accountID);
if (serRef == null)
{
return false;
}
BundleContext bundleContext = getBundleContext();
ProtocolProviderService protocolProvider
= bundleContext.getService(serRef);
try
{
protocolProvider.unregister();
}
catch (OperationFailedException ex)
{
logger.error(
"Failed to unregister protocol provider for account: "
+ accountID + " caused by: " + ex);
}
ServiceRegistration<ProtocolProviderService> registration;
synchronized (registeredAccounts)
{
registration = registeredAccounts.remove(accountID);
}
if (registration == null)
{
return false;
}
// Kill the service.
registration.unregister();
return true;
}
/**
* Initializes and creates an account corresponding to the specified
* accountProperties.
*
* @param accountProperties a set of protocol (or implementation) specific
* properties defining the new account.
* @return the AccountID of the newly created account
*/
public AccountID createAccount(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);
return createAccountID(userID, accountProperties);
}
/**
* 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.
*
* @param accountID the AccountID of the account to remove.
*
* @return true if an account has been removed and false otherwise.
*/
protected boolean removeStoredAccount(AccountID accountID)
{
return getAccountManager().removeStoredAccount(this, accountID);
}
/**
* 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.
* @param sourcePackageName a String containing the package name of the
* concrete factory class that extends us.
* @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.
*/
public static String findAccountPrefix(BundleContext bundleContext,
AccountID accountID,
String sourcePackageName)
{
ServiceReference<ConfigurationService> confReference
= bundleContext.getServiceReference(ConfigurationService.class);
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 (accountID.getAccountUniqueID().equals(accountUID))
{
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()
{
if (logger.isTraceEnabled())
logger.trace("Preparing to stop all protocol providers of" + this);
synchronized (registeredAccounts)
{
for (ServiceRegistration<ProtocolProviderService> reg
: registeredAccounts.values())
{
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<ProtocolProviderService> registeredAccount)
{
ProtocolProviderService protocolProviderService
= getBundleContext().getService(registeredAccount.getReference());
protocolProviderService.shutdown();
}
/**
* Get the <tt>AccountManager</tt> of the protocol.
*
* @return <tt>AccountManager</tt> of the protocol
*/
private AccountManager getAccountManager()
{
BundleContext bundleContext = getBundleContext();
ServiceReference<AccountManager> serviceReference
= bundleContext.getServiceReference(AccountManager.class);
return bundleContext.getService(serviceReference);
}
/**
* Finds registered <tt>ProtocolProviderFactory</tt> for given
* <tt>protocolName</tt>.
* @param bundleContext the OSGI bundle context that will be used.
* @param protocolName the protocol name.
* @return Registered <tt>ProtocolProviderFactory</tt> for given protocol
* name or <tt>null</tt> if no provider was found.
*/
static public ProtocolProviderFactory getProtocolProviderFactory(
BundleContext bundleContext,
String protocolName)
{
Collection<ServiceReference<ProtocolProviderFactory>> serRefs;
String osgiFilter
= "(" + ProtocolProviderFactory.PROTOCOL + "=" + protocolName + ")";
try
{
serRefs
= bundleContext.getServiceReferences(
ProtocolProviderFactory.class,
osgiFilter);
}
catch (InvalidSyntaxException ex)
{
serRefs = null;
logger.error(ex);
}
if ((serRefs == null) || serRefs.isEmpty())
return null;
else
return bundleContext.getService(serRefs.iterator().next());
}
}