Package net.java.sip.communicator.impl.protocol.sip

Source Code of net.java.sip.communicator.impl.protocol.sip.ProtocolProviderServiceSipImpl$ShutdownUnregistrationBlockListener

/*
* 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.sip;

import gov.nist.javax.sip.address.*;
import gov.nist.javax.sip.header.*;
import gov.nist.javax.sip.message.*;
import net.java.sip.communicator.impl.protocol.sip.security.*;
import net.java.sip.communicator.impl.protocol.sip.xcap.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.version.Version;
import net.java.sip.communicator.util.*;
import org.osgi.framework.*;

import javax.sip.*;
import javax.sip.address.*;
import javax.sip.header.*;
import javax.sip.message.*;
import java.net.*;
import java.net.URI;
import java.text.*;
import java.util.*;

/**
* A SIP implementation of the Protocol Provider Service.
*
* @author Emil Ivov
* @author Lubomir Marinov
* @author Alan Kelly
* @author Grigorii Balutsel
*/
public class ProtocolProviderServiceSipImpl
  extends AbstractProtocolProviderService
  implements SipListener,
             RegistrationStateChangeListener
{
    /**
     * Our class logger.
     */
    private static final Logger logger =
        Logger.getLogger(ProtocolProviderServiceSipImpl.class);

    /**
     * The identifier of the account that this provider represents.
     */
    private AccountID accountID = null;

    /**
     * We use this to lock access to initialization.
     */
    private final Object initializationLock = new Object();

    /**
     * indicates whether or not the provider is initialized and ready for use.
     */
    private boolean isInitialized = false;

    /**
     * A list of all events registered for this provider.
     */
    private final List<String> registeredEvents = new ArrayList<String>();

    /**
     * The AddressFactory used to create URLs ans Address objects.
     */
    private AddressFactory addressFactory;

    /**
     * The HeaderFactory used to create SIP message headers.
     */
    private HeaderFactory headerFactory;

    /**
     * The Message Factory used to create SIP messages.
     */
    private SipMessageFactory messageFactory;

    /**
      * The class in charge of event dispatching and managing common JAIN-SIP
      * resources
      */
    private static SipStackSharing sipStackSharing = null;

    /**
     * A table mapping SIP methods to method processors (every processor must
     * implement the SipListener interface). Whenever a new message arrives we
     * extract its method and hand it to the processor instance registered
     */
    private final Hashtable<String, List<MethodProcessor>> methodProcessors =
        new Hashtable<String, List<MethodProcessor>>();

    /**
     * The name of the property under which the user may specify the number of
     * the port where they would prefer us to bind our sip socket.
     */
    private static final String PREFERRED_SIP_PORT =
        "net.java.sip.communicator.service.protocol.sip.PREFERRED_SIP_PORT";

    /**
     * Property used in default settings if we want to override the system
     * property for java.net.preferIPv6Addresses, with values true or false.
     */
    private static final String PREFER_IPV6_ADDRESSES =
        "net.java.sip.communicator.impl.protocol.sip.PREFER_IPV6_ADDRESSES";

    /**
     * The name of the property under which the user may specify the number of
     * seconds that registrations take to expire.
     */
    private static final String REGISTRATION_EXPIRATION =
        "net.java.sip.communicator.impl.protocol.sip.REGISTRATION_EXPIRATION";

    /**
     * The name of the property under which the user may specify a transport
     * to use for destinations whose preferred transport is unknown.
     */
    private static final String DEFAULT_TRANSPORT
        = "net.java.sip.communicator.impl.protocol.sip.DEFAULT_TRANSPORT";

    /**
     * The name of the property under which the user may specify if the desktop
     * streaming or sharing should be disabled.
     */
    private static final String IS_DESKTOP_STREAMING_DISABLED
        = "net.java.sip.communicator.impl.protocol.sip.DESKTOP_STREAMING_DISABLED";

    /**
     * The name of the property under which the user may specify if the video
     * calls should be disabled.
     */
    private static final String IS_CALLING_DISABLED
        = "net.java.sip.communicator.impl.protocol.sip.CALLING_DISABLED";

    /**
     * Default number of times that our requests can be forwarded.
     */
    private static final int  MAX_FORWARDS = 70;

    /**
     * Keep-alive method can be - register,options or udp
     */
    public static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD";

    /**
     * The interval for keep-alive
     */
    public static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL";

    /**
     * The name of the property under which the user may specify whether to use
     * or not XCAP.
     */
    public static final String XCAP_ENABLE = "XCAP_ENABLE";

    /**
     * The name of the property under which the user may specify whether to use
     * original sip creadetials for the XCAP.
     */
    public static final String XCAP_USE_SIP_CREDETIALS =
            "XCAP_USE_SIP_CREDETIALS";

    /**
     * The name of the property under which the user may specify the XCAP server
     * uri.
     */
    public static final String XCAP_SERVER_URI = "XCAP_SERVER_URI";

    /**
     * The name of the property under which the user may specify the XCAP user.
     */
    public static final String XCAP_USER = "XCAP_USER";

    /**
     * The name of the property under which the user may specify the XCAP user
     * password.
     */
    public static final String XCAP_PASSWORD = "XCAP_PASSWORD";

    /**
     * Presence content for image.
     */
    public static final String PRES_CONTENT_IMAGE_NAME = "sip_communicator";

    /**
     * The default maxForwards header that we use in our requests.
     */
    private MaxForwardsHeader maxForwardsHeader = null;

    /**
     * The header that we use to identify ourselves.
     */
    private UserAgentHeader userAgentHeader = null;

    /**
     * The name that we want to send others when calling or chatting with them.
     */
    private String ourDisplayName = null;

    /**
     * Our current connection with the registrar.
     */
    private SipRegistrarConnection sipRegistrarConnection = null;

    /**
     * The SipSecurityManager instance that would be taking care of our
     * authentications.
     */
    private SipSecurityManager sipSecurityManager = null;

    /**
     * The string representing our outbound proxy if we have one (remains null
     * if we are not using a proxy).
     */
    private String outboundProxyString = null;

    /**
     * The address and port of an outbound proxy if we have one (remains null
     * if we are not using a proxy).
     */
    private InetSocketAddress outboundProxySocketAddress = null;

    /**
     * The transport used by our outbound proxy (remains null
     * if we are not using a proxy).
     */
    private String outboundProxyTransport = null;

    /**
     * The logo corresponding to the jabber protocol.
     */
    private ProtocolIconSipImpl protocolIcon;

    /**
     * The presence status set supported by this provider
     */
    private SipStatusEnum sipStatusEnum;

    /**
     * The XCAP client.
     */
    private final XCapClient xCapClient = new XCapClientImpl();

    /**
     * A list of early processors that can do early processing of received
     * messages (requests or responses).
     */
    private final List<SipMessageProcessor> earlyProcessors =
        new ArrayList<SipMessageProcessor>();

    /**
     * Gets the XCAP client.
     *
     * @return the XCAP client.
     */
    public XCapClient getXCapClient()
    {
        return xCapClient;
    }

    /**
     * Returns the AccountID that uniquely identifies the account represented by
     * this instance of the ProtocolProviderService.
     * @return the id of the account represented by this provider.
     */
    public AccountID getAccountID()
    {
        return accountID;
    }

    /**
     * Returns the state of the registration of this protocol provider with the
     * corresponding registration service.
     * @return ProviderRegistrationState
     */
    public RegistrationState getRegistrationState()
    {
        if(this.sipRegistrarConnection == null )
        {
            return RegistrationState.UNREGISTERED;
        }
        return sipRegistrarConnection.getRegistrationState();
    }

    /**
     * Returns the short name of the protocol that the implementation of this
     * provider is based upon (like SIP, Jabber, ICQ/AIM,  or others for
     * example). If the name of the protocol has been enumerated in
     * ProtocolNames then the value returned by this method must be the same as
     * the one in ProtocolNames.
     * @return a String containing the short name of the protocol this service
     * is implementing (most often that would be a name in ProtocolNames).
     */
    public String getProtocolName()
    {
        return ProtocolNames.SIP;
    }

    /**
     * Register a new event taken in account by this provider. This is usefull
     * to generate the Allow-Events header of the OPTIONS responses and to
     * generate 489 responses.
     *
     * @param event The event to register
     */
    public void registerEvent(String event)
    {
        synchronized (this.registeredEvents)
        {
            if (!this.registeredEvents.contains(event))
            {
                this.registeredEvents.add(event);
            }
        }
    }

    /**
     * Returns the list of all the registered events for this provider.
     *
     * @return The list of all the registered events
     */
    public List<String> getKnownEventsList()
    {
        return this.registeredEvents;
    }

    /**
     * Overrides
     * {@link AbstractProtocolProviderService#fireRegistrationStateChanged(
     * RegistrationState, RegistrationState, int, String)} in order to add
     * enabling/disabling XCAP functionality in accord with the current
     * <tt>RegistrationState</tt> of this
     * <tt>ProtocolProviderServiceSipImpl</tt>.
     *
     * @param oldState the state that the provider had before the change
     * occurred
     * @param newState the state that the provider is currently in
     * @param reasonCode a value corresponding to one of the REASON_XXX fields
     * of the <tt>RegistrationStateChangeEvent</tt> class, indicating the reason
     * for this state transition
     * @param reason a <tt>String</tt> further explaining the reason code or
     * <tt>null</tt> if no such explanation is necessary
     * @see AbstractProtocolProviderService#fireRegistrationStateChanged(
     * RegistrationState, RegistrationState, int, String)
     */
    @Override
    public void fireRegistrationStateChanged(RegistrationState oldState,
                                             RegistrationState newState,
                                             int reasonCode,
                                             String reason)
    {
        if (newState.equals(RegistrationState.REGISTERED))
        {
            try
            {
                boolean enableXCap =
                        accountID.getAccountPropertyBoolean(XCAP_ENABLE, true);
                boolean useSipCredetials =
                        accountID.getAccountPropertyBoolean(
                                XCAP_USE_SIP_CREDETIALS, true);
                String serverUri =
                        accountID.getAccountPropertyString(XCAP_SERVER_URI);
                String username = accountID.getAccountPropertyString(
                                  ProtocolProviderFactory.USER_ID);
                Address userAddress = parseAddressString(username);
                String password;
                if (useSipCredetials)
                {
                    username = ((SipUri)userAddress.getURI()).getUser();
                    password = SipActivator.getProtocolProviderFactory().
                            loadPassword(accountID);
                }
                else
                {
                    username = accountID.getAccountPropertyString(XCAP_USER);
                    password = accountID.getAccountPropertyString(XCAP_PASSWORD);
                }
                // Connect to xcap server
                if(enableXCap && serverUri != null)
                {
                    URI uri = new URI(serverUri.trim());
                    if(uri.getHost() != null && uri.getPath() != null)
                    {
                        xCapClient.connect(uri, userAddress, username, password);
                    }
                }
            }
            catch (Exception e)
            {
                logger.error("Error while connecting to XCAP server. " +
                        "Contact list won't be saved", e);
            }
        }
        else if (newState.equals(RegistrationState.UNREGISTERING) ||
                newState.equals(RegistrationState.CONNECTION_FAILED))
        {
            xCapClient.disconnect();
        }

        super.fireRegistrationStateChanged(oldState, newState, reasonCode,
                reason);
    }

    /**
     * Starts the registration process. Connection details such as
     * registration server, user name/number are provided through the
     * configuration service through implementation specific properties.
     *
     * @param authority the security authority that will be used for resolving
     *        any security challenges that may be returned during the
     *        registration or at any moment while wer're registered.
     *
     * @throws OperationFailedException with the corresponding code it the
     * registration fails for some reason (e.g. a networking error or an
     * implementation problem).
     */
    public void register(SecurityAuthority authority)
        throws OperationFailedException
    {
        if(!isInitialized)
        {
            throw new OperationFailedException(
                "Provided must be initialized before being able to register."
                , OperationFailedException.GENERAL_ERROR);
        }

        if (isRegistered())
        {
            return;
        }

        sipStackSharing.addSipListener(this);
        // be warned when we will unregister, so that we can
        // then remove us as SipListener
        this.addRegistrationStateChangeListener(this);

        // Enable the user name modification. Setting this property to true
        // we'll allow the user to change the user name stored in the given
        //authority.
        authority.setUserNameEditable(true);

        //init the security manager before doing the actual registration to
        //avoid being asked for credentials before being ready to provide them
        sipSecurityManager.setSecurityAuthority(authority);

        // We check here if the sipRegistrarConnection is initialized. This is
        // needed in case that in the initialization process we had no internet
        // connection and resolving the registrar failed.
        if (sipRegistrarConnection == null)
            initRegistrarConnection((SipAccountID) accountID);

        // The same here, we check if the outbound proxy is initialized in case
        // through the initialization process there was no internet connection.
        if (outboundProxySocketAddress == null)
            initOutboundProxy((SipAccountID)accountID, 0);

        //connect to the Registrar.
        if (sipRegistrarConnection != null)
            sipRegistrarConnection.register();
    }

    /**
     * Ends the registration of this protocol provider with the current
     * registration service.
     *
     * @throws OperationFailedException with the corresponding code it the
     * registration fails for some reason (e.g. a networking error or an
     * implementation problem).
     */
    public void unregister()
        throws OperationFailedException
    {
        if(getRegistrationState().equals(RegistrationState.UNREGISTERED)
            || getRegistrationState().equals(RegistrationState.UNREGISTERING)
                || getRegistrationState().equals(RegistrationState.CONNECTION_FAILED))
        {
            return;
        }

        sipRegistrarConnection.unregister();
        sipSecurityManager.setSecurityAuthority(null);
    }

    /**
     * Initializes the service implementation, and puts it in a state where it
     * could interoperate with other services.
     *
     * @param sipAddress the account id/uin/screenname of the account that we're
     * about to create
     * @param accountID the identifier of the account that this protocol
     * provider represents.
     *
     * @throws OperationFailedException with code INTERNAL_ERROR if we fail
     * initializing the SIP Stack.
     * @throws java.lang.IllegalArgumentException if one or more of the account
     * properties have invalid values.
     *
     * @see net.java.sip.communicator.service.protocol.AccountID
     */
    protected void initialize(String    sipAddress,
                              SipAccountID accountID)
        throws OperationFailedException, IllegalArgumentException
    {
        synchronized (initializationLock)
        {

            this.accountID = accountID;

            String protocolIconPath =
                accountID
                    .getAccountPropertyString(ProtocolProviderFactory.PROTOCOL_ICON_PATH);

            if (protocolIconPath == null)
                protocolIconPath = "resources/images/protocol/sip";

            this.protocolIcon = new ProtocolIconSipImpl(protocolIconPath);

            this.sipStatusEnum = new SipStatusEnum(protocolIconPath);

            boolean isProxyValidated =
                accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.PROXY_ADDRESS_VALIDATED, false);
            //init the proxy, we had to have at least one address configured
            // so use it, if it fails later we will use next one
            if(!isProxyValidated)
                initOutboundProxy(accountID, 0);

            //init proxy port
            int preferredSipPort = ListeningPoint.PORT_5060;

            String proxyPortStr = SipActivator.getConfigurationService().
                    getString(PREFERRED_SIP_PORT);

            if (proxyPortStr != null && proxyPortStr.length() > 0)
            {
                try
                {
                    preferredSipPort = Integer.parseInt(proxyPortStr);
                }
                catch (NumberFormatException ex)
                {
                    logger.error(
                        proxyPortStr
                        + " is not a valid port value. Expected an integer"
                        , ex);
                }

                if (preferredSipPort > NetworkUtils.MAX_PORT_NUMBER)
                {
                    logger.error(preferredSipPort + " is larger than "
                                 + NetworkUtils.MAX_PORT_NUMBER
                                 + " and does not "
                                 + "therefore represent a valid port number.");
                }
            }

            if(sipStackSharing == null)
                sipStackSharing = new SipStackSharing();

            // get the presence options
            boolean enablePresence =
                accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.IS_PRESENCE_ENABLED, true);

            boolean forceP2P = accountID.getAccountPropertyBoolean(
                            ProtocolProviderFactory.FORCE_P2P_MODE, true);

            int pollingValue =
                accountID.getAccountPropertyInt(
                    ProtocolProviderFactory.POLLING_PERIOD, 30);

            int subscriptionExpiration =
                accountID.getAccountPropertyInt(
                    ProtocolProviderFactory.SUBSCRIPTION_EXPIRATION, 3600);

            //create SIP factories.
            headerFactory = new HeaderFactoryImpl();
            addressFactory = new AddressFactoryImpl();

            //initialize our display name
            ourDisplayName = accountID.getAccountPropertyString(
                                    ProtocolProviderFactory.DISPLAY_NAME);

            if(ourDisplayName == null
               || ourDisplayName.trim().length() == 0)
            {
                ourDisplayName = accountID.getUserID();
            }

            boolean isServerValidated =
                accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.SERVER_ADDRESS_VALIDATED, false);

            //create a connection with the registrar
            if(!isServerValidated)
                initRegistrarConnection(accountID);

            //init our call processor
            OperationSetBasicTelephonySipImpl opSetBasicTelephonySipImpl
                = new OperationSetBasicTelephonySipImpl(this);

            boolean isCallingDisabled
                = SipActivator.getConfigurationService()
                    .getBoolean(IS_CALLING_DISABLED, false);

            boolean isCallingDisabledForAccount
                = accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
                    false);

            if (!isCallingDisabled && !isCallingDisabledForAccount)
            {
                addSupportedOperationSet(
                    OperationSetBasicTelephony.class,
                    opSetBasicTelephonySipImpl);

                addSupportedOperationSet(
                    OperationSetAdvancedTelephony.class,
                    opSetBasicTelephonySipImpl);

                // init ZRTP (OperationSetBasicTelephonySipImpl implements
                // OperationSetSecureTelephony)
                addSupportedOperationSet(
                    OperationSetSecureTelephony.class,
                    opSetBasicTelephonySipImpl);

                // OperationSetVideoTelephony
                addSupportedOperationSet(
                    OperationSetVideoTelephony.class,
                    new OperationSetVideoTelephonySipImpl(
                            opSetBasicTelephonySipImpl));

                addSupportedOperationSet(
                    OperationSetTelephonyConferencing.class,
                    new OperationSetTelephonyConferencingSipImpl(this));

                // init DTMF (from JM Heitz)
                addSupportedOperationSet(
                    OperationSetDTMF.class,
                    new OperationSetDTMFSipImpl(this));

                boolean isDesktopStreamingDisabled
                    = SipActivator.getConfigurationService()
                        .getBoolean(IS_DESKTOP_STREAMING_DISABLED, false);

                boolean isAccountDesktopStreamingDisabled
                    = accountID.getAccountPropertyBoolean(
                        ProtocolProviderFactory.IS_DESKTOP_STREAMING_DISABLED,
                        false);

                if (!isDesktopStreamingDisabled
                    && !isAccountDesktopStreamingDisabled)
                {
                    // OperationSetDesktopStreaming
                    addSupportedOperationSet(
                        OperationSetDesktopStreaming.class,
                        new OperationSetDesktopStreamingSipImpl(
                                opSetBasicTelephonySipImpl));

                    // OperationSetDesktopSharingServer
                    addSupportedOperationSet(
                       OperationSetDesktopSharingServer.class,
                       new OperationSetDesktopSharingServerSipImpl(
                               opSetBasicTelephonySipImpl));

                    // OperationSetDesktopSharingClient
                    addSupportedOperationSet(
                        OperationSetDesktopSharingClient.class,
                        new OperationSetDesktopSharingClientSipImpl(this));
                }
            }

            //init presence op set.
            OperationSetPersistentPresence opSetPersPresence
                = new OperationSetPresenceSipImpl(this, enablePresence,
                        forceP2P, pollingValue, subscriptionExpiration);

            addSupportedOperationSet(
                OperationSetPersistentPresence.class,
                opSetPersPresence);
            //also register with standard presence
            addSupportedOperationSet(
                OperationSetPresence.class,
                opSetPersPresence);

            if (enablePresence)
            {
                // init instant messaging
                OperationSetBasicInstantMessagingSipImpl opSetBasicIM =
                    new OperationSetBasicInstantMessagingSipImpl(this);

                addSupportedOperationSet(
                    OperationSetBasicInstantMessaging.class,
                    opSetBasicIM);

                // init typing notifications
                addSupportedOperationSet(
                    OperationSetTypingNotifications.class,
                    new OperationSetTypingNotificationsSipImpl(
                            this,
                            opSetBasicIM));
                OperationSetServerStoredAccountInfo opSetSSAccountInfo =
                    new OperationSetServerStoredAccountInfoSipImpl(this);
                // init avatar
                addSupportedOperationSet(
                    OperationSetServerStoredAccountInfo.class,
                    opSetSSAccountInfo);

                addSupportedOperationSet(
                    OperationSetAvatar.class,
                    new OperationSetAvatarSipImpl(this, opSetSSAccountInfo));
            }

            addSupportedOperationSet(
                OperationSetMessageWaiting.class,
                new OperationSetMessageWaitingSipImpl(this));

            //initialize our OPTIONS handler
            new ClientCapabilities(this);

            //init the security manager
            this.sipSecurityManager = new SipSecurityManager(accountID);
            sipSecurityManager.setHeaderFactory(headerFactory);

            // register any available custom extensions
            ProtocolProviderExtensions.registerCustomOperationSets(this);

            isInitialized = true;
        }
    }

    /**
     * Adds a specific <tt>OperationSet</tt> implementation to the set of
     * supported <tt>OperationSet</tt>s of this instance. Serves as a type-safe
     * wrapper around {@link #supportedOperationSets} which works with class
     * names instead of <tt>Class</tt> and also shortens the code which performs
     * such additions.
     *
     * @param <T> the exact type of the <tt>OperationSet</tt> implementation to
     * be added
     * @param opsetClass the <tt>Class</tt> of <tt>OperationSet</tt> under the
     * name of which the specified implementation is to be added
     * @param opset the <tt>OperationSet</tt> implementation to be added
     */
    @Override
    protected <T extends OperationSet> void addSupportedOperationSet(
            Class<T> opsetClass,
            T opset)
    {
        super.addSupportedOperationSet(opsetClass, opset);
    }

    /**
     * Removes an <tt>OperationSet</tt> implementation from the set of
     * supported <tt>OperationSet</tt>s for this instance.
     *
     * @param <T> the exact type of the <tt>OperationSet</tt> implementation to
     * be added
     * @param opsetClass the <tt>Class</tt> of <tt>OperationSet</tt> under the
     * name of which the specified implementation is to be added
     */
    @Override
    protected <T extends OperationSet> void removeSupportedOperationSet(
                                                Class<T> opsetClass)
    {
        super.removeSupportedOperationSet(opsetClass);
    }

    /**
     * Never called.
     *
     * @param exceptionEvent the IOExceptionEvent containing the cause.
     */
    public void processIOException(IOExceptionEvent exceptionEvent) {}

    /**
     * Processes a Response received on a SipProvider upon which this
     * SipListener is registered.
     * <p>
     *
     * @param responseEvent the responseEvent fired from the SipProvider to the
     * SipListener representing a Response received from the network.
     */
    public void processResponse(ResponseEvent responseEvent)
    {
        ClientTransaction clientTransaction = responseEvent
            .getClientTransaction();
        if (clientTransaction == null)
        {
            if (logger.isDebugEnabled())
                logger.debug("ignoring a transactionless response");
            return;
        }

        Response response = responseEvent.getResponse();

        earlyProcessMessage(responseEvent);

        String method = ( (CSeqHeader) response.getHeader(CSeqHeader.NAME))
            .getMethod();

        //find the object that is supposed to take care of responses with the
        //corresponding method
        List<MethodProcessor> processors = methodProcessors.get(method);

        if (processors != null)
        {
            if (logger.isDebugEnabled())
                logger.debug("Found " + processors.size()
                        + " processor(s) for method " + method);

            for (MethodProcessor processor : processors)
                if (processor.processResponse(responseEvent))
                    break;
        }
    }

    /**
     * Processes a retransmit or expiration Timeout of an underlying
     * {@link Transaction} handled by this SipListener. This Event notifies the
     * application that a retransmission or transaction Timer expired in the
     * SipProvider's transaction state machine. The TimeoutEvent encapsulates
     * the specific timeout type and the transaction identifier either client or
     * server upon which the timeout occurred. The type of Timeout can by
     * determined by:
     * <code>timeoutType = timeoutEvent.getTimeout().getValue();</code>
     *
     * @param timeoutEvent -
     *            the timeoutEvent received indicating either the message
     *            retransmit or transaction timed out.
     */
    public void processTimeout(TimeoutEvent timeoutEvent)
    {
        Transaction transaction;
        if(timeoutEvent.isServerTransaction())
            transaction = timeoutEvent.getServerTransaction();
        else
            transaction = timeoutEvent.getClientTransaction();

        if (transaction == null)
        {
            if (logger.isDebugEnabled())
                logger.debug("ignoring a transactionless timeout event");
            return;
        }

        earlyProcessMessage(timeoutEvent);

        Request request = transaction.getRequest();
        if (logger.isDebugEnabled())
            logger.debug("received timeout for req=" + request);

        //find the object that is supposed to take care of responses with the
        //corresponding method
        String method = request.getMethod();
        List<MethodProcessor> processors = methodProcessors.get(method);

        if (processors != null)
        {
            if (logger.isDebugEnabled())
                logger.debug("Found " + processors.size()
                + " processor(s) for method " + method);

            for (MethodProcessor processor : processors)
            {
                if (processor.processTimeout(timeoutEvent))
                {
                    break;
                }
            }
        }
    }

    /**
     * Process an asynchronously reported TransactionTerminatedEvent.
     * When a transaction transitions to the Terminated state, the stack
     * keeps no further records of the transaction. This notification can be used by
     * applications to clean up any auxiliary data that is being maintained
     * for the given transaction.
     *
     * @param transactionTerminatedEvent -- an event that indicates that the
     *       transaction has transitioned into the terminated state.
     * @since v1.2
     */
    public void processTransactionTerminated(TransactionTerminatedEvent
                                             transactionTerminatedEvent)
    {
        Transaction transaction;
        if(transactionTerminatedEvent.isServerTransaction())
            transaction = transactionTerminatedEvent.getServerTransaction();
        else
            transaction = transactionTerminatedEvent.getClientTransaction();

        if (transaction == null)
        {
            if (logger.isDebugEnabled())
                logger.debug(
                        "ignoring a transactionless transaction terminated event");
            return;
        }

        Request request = transaction.getRequest();

        //find the object that is supposed to take care of responses with the
        //corresponding method
        String method = request.getMethod();
        List<MethodProcessor> processors = methodProcessors.get(method);

        if (processors != null)
        {
            if (logger.isDebugEnabled())
                logger.debug("Found " + processors.size()
                        + " processor(s) for method " + method);

            for (MethodProcessor processor : processors)
            {
                if (processor.processTransactionTerminated(
                    transactionTerminatedEvent))
                {
                    break;
                }
            }
        }
    }

    /**
     * Process an asynchronously reported DialogTerminatedEvent.
     * When a dialog transitions to the Terminated state, the stack
     * keeps no further records of the dialog. This notification can be used by
     * applications to clean up any auxiliary data that is being maintained
     * for the given dialog.
     *
     * @param dialogTerminatedEvent -- an event that indicates that the
     *       dialog has transitioned into the terminated state.
     * @since v1.2
     */
    public void processDialogTerminated(DialogTerminatedEvent
                                        dialogTerminatedEvent)
    {
        if (logger.isDebugEnabled())
            logger.debug("Dialog terminated for req="
                    + dialogTerminatedEvent.getDialog());
    }

    /**
     * Processes a Request received on a SipProvider upon which this SipListener
     * is registered.
     * <p>
     * @param requestEvent requestEvent fired from the SipProvider to the
     * SipListener representing a Request received from the network.
     */
    public void processRequest(RequestEvent requestEvent)
    {
        Request request = requestEvent.getRequest();

        if(getRegistrarConnection() != null
           && !getRegistrarConnection().isRegistrarless()
           && !getRegistrarConnection().isRequestFromSameConnection(request))
        {
            logger.warn("Received request not from our proxy, ignoring it! "
                + "Request:" + request);
            if (requestEvent.getServerTransaction() != null)
            {
                try
                {
                    requestEvent.getServerTransaction().terminate();
                }
                catch (Throwable e)
                {
                    logger.warn("Failed to properly terminate transaction for "
                                    +"a rogue request. Well ... so be it "
                                    + "Request:" + request);
                }
            }
            return;
        }

        earlyProcessMessage(requestEvent);

        // test if an Event header is present and known
        EventHeader eventHeader = (EventHeader)
            request.getHeader(EventHeader.NAME);

        if (eventHeader != null)
        {
            boolean eventKnown;

            synchronized (this.registeredEvents)
            {
                eventKnown = this.registeredEvents.contains(
                        eventHeader.getEventType());
            }

            if (!eventKnown)
            {
                // send a 489 / Bad Event response
                ServerTransaction serverTransaction = requestEvent
                    .getServerTransaction();

                if (serverTransaction == null)
                {
                    try
                    {
                        serverTransaction = SipStackSharing.
                            getOrCreateServerTransaction(requestEvent);
                    }
                    catch (TransactionAlreadyExistsException ex)
                    {
                        //let's not scare the user and only log a message
                        logger.error("Failed to create a new server"
                            + "transaction for an incoming request\n"
                            + "(Next message contains the request)"
                            , ex);
                        return;
                    }
                    catch (TransactionUnavailableException ex)
                    {
                        //let's not scare the user and only log a message
                        logger.error("Failed to create a new server"
                            + "transaction for an incoming request\n"
                            + "(Next message contains the request)"
                            , ex);
                            return;
                    }
                }

                Response response = null;
                try
                {
                    response = this.getMessageFactory().createResponse(
                            Response.BAD_EVENT, request);
                }
                catch (ParseException e)
                {
                    logger.error("failed to create the 489 response", e);
                    return;
                }

                try
                {
                    serverTransaction.sendResponse(response);
                    return;
                }
                catch (SipException e)
                {
                    logger.error("failed to send the response", e);
                }
                catch (InvalidArgumentException e)
                {
                    // should not happen
                    logger.error("invalid argument provided while trying" +
                            " to send the response", e);
                }
            }
        }


        String method = request.getMethod();

        //find the object that is supposed to take care of responses with the
        //corresponding method
        List<MethodProcessor> processors = methodProcessors.get(method);

        //raise this flag if at least one processor handles the request.
        boolean processedAtLeastOnce = false;

        if (processors != null)
        {
            if (logger.isDebugEnabled())
                logger.debug("Found " + processors.size()
                        + " processor(s) for method " + method);

            for (MethodProcessor processor : processors)
            {
                if (processor.processRequest(requestEvent))
                {
                    processedAtLeastOnce = true;
                    break;
                }
            }
        }

        //send an error response if no one processes this
        if (!processedAtLeastOnce)
        {
            ServerTransaction serverTransaction;
            try
            {
                serverTransaction = SipStackSharing.getOrCreateServerTransaction(requestEvent);

                if (serverTransaction == null)
                {
                    logger.warn("Could not create a transaction for a "
                               +"non-supported method " + request.getMethod());
                    return;
                }

                TransactionState state = serverTransaction.getState();

                if( TransactionState.TRYING.equals(state))
                {
                    Response response = this.getMessageFactory().createResponse(
                                    Response.NOT_IMPLEMENTED, request);
                    serverTransaction.sendResponse(response);
                }
            }
            catch (Throwable exc)
            {
                logger.warn("Could not respond to a non-supported method "
                                + request.getMethod(), exc);
            }
        }
    }

    /**
     * Makes the service implementation close all open sockets and release
     * any resources that it might have taken and prepare for shutdown/garbage
     * collection.
     */
    public void shutdown()
    {
        if(!isInitialized)
        {
            return;
        }

        // don't run in Thread cause shutting down may finish before
        // we were able to unregister
        new ShutdownThread().run();
    }

    /**
     * The thread that we use in order to send our unREGISTER request upon
     * system shut down.
     */
    protected class ShutdownThread implements Runnable
    {
        /**
         * Shutdowns operation sets that need it then calls the
         * <tt>SipRegistrarConnection.unregister()</tt> method.
         */
        public void run()
        {
            if (logger.isTraceEnabled())
                logger.trace("Killing the SIP Protocol Provider.");
            //kill all active calls
            OperationSetBasicTelephonySipImpl telephony
                = (OperationSetBasicTelephonySipImpl)getOperationSet(
                    OperationSetBasicTelephony.class);
            telephony.shutdown();

            if(isRegistered())
            {
                try
                {
                    //create a listener that would notify us when
                    //un-registration has completed.
                    ShutdownUnregistrationBlockListener listener
                        = new ShutdownUnregistrationBlockListener();
                    addRegistrationStateChangeListener(listener);

                    //do the un-registration
                    unregister();

                    //leave ourselves time to complete un-registration (may include
                    //2 REGISTER requests in case notification is needed.)
                    listener.waitForEvent(3000L);
                }
                catch (OperationFailedException ex)
                {
                    //we're shutting down so we need to silence the exception here
                    logger.error(
                        "Failed to properly unregister before shutting down. "
                        + getAccountID()
                        , ex);
                }
            }

            headerFactory = null;
            messageFactory = null;
            addressFactory = null;
            sipSecurityManager = null;

            methodProcessors.clear();

            isInitialized = false;
        }
    }

    /**
     * Initializes and returns an ArrayList with a single ViaHeader
     * containing a localhost address usable with the specified
     * s<tt>destination</tt>. This ArrayList may be used when sending
     * requests to that destination.
     * <p>
     * @param intendedDestination The address of the destination that the
     * request using the via headers will be sent to.
     *
     * @return ViaHeader-s list to be used when sending requests.
     * @throws OperationFailedException code INTERNAL_ERROR if a ParseException
     * occurs while initializing the array list.
     *
     */
    public ArrayList<ViaHeader> getLocalViaHeaders(Address intendedDestination)
        throws OperationFailedException
    {
        return getLocalViaHeaders((SipURI)intendedDestination.getURI());
    }

    /**
     * Initializes and returns an ArrayList with a single ViaHeader
     * containing a localhost address usable with the specified
     * s<tt>destination</tt>. This ArrayList may be used when sending
     * requests to that destination.
     * <p>
     * @param intendedDestination The address of the destination that the
     * request using the via headers will be sent to.
     *
     * @return ViaHeader-s list to be used when sending requests.
     * @throws OperationFailedException code INTERNAL_ERROR if a ParseException
     * occurs while initializing the array list.
     *
     */
    public ArrayList<ViaHeader> getLocalViaHeaders(SipURI intendedDestination)
        throws OperationFailedException
    {
        ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();

        ListeningPoint srcListeningPoint
                = getListeningPoint(intendedDestination.getTransportParam());
        try
        {
            InetSocketAddress targetAddress =
                        getIntendedDestination(intendedDestination);

            InetAddress localAddress = SipActivator
                .getNetworkAddressManagerService().getLocalHost(
                    targetAddress.getAddress());

            int localPort = srcListeningPoint.getPort();
            String transport = srcListeningPoint.getTransport();
            if (ListeningPoint.TCP.equalsIgnoreCase(transport))
                //|| ListeningPoint.TLS.equalsIgnoreCase(transport)
            {
                InetSocketAddress localSockAddr
                    = sipStackSharing.getLocalAddressForDestination(
                                    targetAddress.getAddress(),
                                    targetAddress.getPort(),
                                    localAddress,
                                    transport);
                localPort = localSockAddr.getPort();
            }

            ViaHeader viaHeader = headerFactory.createViaHeader(
                localAddress.getHostAddress(),
                localPort,
                transport,
                null
                );
            viaHeaders.add(viaHeader);
            if (logger.isDebugEnabled())
                logger.debug("generated via headers:" + viaHeader);
            return viaHeaders;
        }
        catch (ParseException ex)
        {
            logger.error(
                "A ParseException occurred while creating Via Headers!", ex);
            throw new OperationFailedException(
                "A ParseException occurred while creating Via Headers!"
                ,OperationFailedException.INTERNAL_ERROR
                ,ex);
        }
        catch (InvalidArgumentException ex)
        {
            logger.error(
                "Unable to create a via header for port "
                + sipStackSharing.getLP(ListeningPoint.UDP).getPort(),
                ex);
            throw new OperationFailedException(
                "Unable to create a via header for port "
                + sipStackSharing.getLP(ListeningPoint.UDP).getPort()
                ,OperationFailedException.INTERNAL_ERROR
                ,ex);
        }
        catch (java.io.IOException ex)
        {
            logger.error(
                "Unable to create a via header for port "
                + sipStackSharing.getLP(ListeningPoint.UDP).getPort(),
                ex);
            throw new OperationFailedException(
                "Unable to create a via header for port "
                + sipStackSharing.getLP(ListeningPoint.UDP).getPort()
                ,OperationFailedException.NETWORK_FAILURE
                ,ex);
        }
    }

    /**
     * Initializes and returns this provider's default maxForwardsHeader field
     * using the value specified by MAX_FORWARDS.
     *
     * @return an instance of a MaxForwardsHeader that can be used when
     * sending requests
     *
     * @throws OperationFailedException with code INTERNAL_ERROR if MAX_FORWARDS
     * has an invalid value.
     */
    public MaxForwardsHeader getMaxForwardsHeader() throws
        OperationFailedException
    {
        if (maxForwardsHeader == null)
        {
            try
            {
                maxForwardsHeader = headerFactory.createMaxForwardsHeader(
                    MAX_FORWARDS);
                if (logger.isDebugEnabled())
                    logger.debug("generated max forwards: "
                            + maxForwardsHeader.toString());
            }
            catch (InvalidArgumentException ex)
            {
                throw new OperationFailedException(
                    "A problem occurred while creating MaxForwardsHeader"
                    , OperationFailedException.INTERNAL_ERROR
                    , ex);
            }
        }

        return maxForwardsHeader;
    }

    /**
     * Returns a Contact header containing a sip URI based on a localhost
     * address.
     *
     * @param intendedDestination the destination that we plan to be sending
     * this contact header to.
     *
     * @return a Contact header based upon a local inet address.
     */
    public ContactHeader getContactHeader(Address intendedDestination)
    {
        return getContactHeader((SipURI)intendedDestination.getURI());
    }

    /**
     * Returns a Contact header containing a sip URI based on a localhost
     * address and therefore usable in REGISTER requests only.
     *
     * @param intendedDestination the destination that we plan to be sending
     * this contact header to.
     *
     * @return a Contact header based upon a local inet address.
     */
    public ContactHeader getContactHeader(SipURI intendedDestination)
    {
        ContactHeader registrationContactHeader = null;

        ListeningPoint srcListeningPoint
                                  = getListeningPoint(intendedDestination);

        InetSocketAddress targetAddress =
                getIntendedDestination(intendedDestination);

        try
        {
            //find the address to use with the target
            InetAddress localAddress = SipActivator
                .getNetworkAddressManagerService()
                .getLocalHost(targetAddress.getAddress());

            SipURI contactURI = addressFactory.createSipURI(
                getAccountID().getUserID()
                , localAddress.getHostAddress() );

            String transport = srcListeningPoint.getTransport();
            contactURI.setTransportParam(transport);

            int localPort = srcListeningPoint.getPort();

            //if we are using tcp, make sure that we include the port of the
            //socket that we are actually using and not that of LP
            if (ListeningPoint.TCP.equalsIgnoreCase(transport)
                || ListeningPoint.TLS.equalsIgnoreCase(transport))
            {
                InetSocketAddress localSockAddr
                    = sipStackSharing.getLocalAddressForDestination(
                                    targetAddress.getAddress(),
                                    targetAddress.getPort(),
                                    localAddress,
                                    transport);
                localPort = localSockAddr.getPort();
            }

            contactURI.setPort(localPort);

            // set a custom param to ease incoming requests dispatching in case
            // we have several registrar accounts with the same username
            String paramValue = getContactAddressCustomParamValue();
            if (paramValue != null)
            {
                contactURI.setParameter(
                        SipStackSharing.CONTACT_ADDRESS_CUSTOM_PARAM_NAME,
                        paramValue);
            }

            Address contactAddress = addressFactory.createAddress( contactURI );

            String ourDisplayName = getOurDisplayName();
            if (ourDisplayName != null)
            {
                contactAddress.setDisplayName(ourDisplayName);
            }
            registrationContactHeader = headerFactory.createContactHeader(
                contactAddress);

            if (logger.isDebugEnabled())
                logger.debug("generated contactHeader:"
                        + registrationContactHeader);
        }
        catch (ParseException ex)
        {
            logger.error(
                "A ParseException occurred while creating From Header!", ex);
            throw new IllegalArgumentException(
                "A ParseException occurred while creating From Header!"
                , ex);
        }
        catch (java.io.IOException ex)
        {
            logger.error(
                "A ParseException occurred while creating From Header!", ex);
            throw new IllegalArgumentException(
                "A ParseException occurred while creating From Header!"
                , ex);
        }
        return registrationContactHeader;
    }

    /**
     * Returns null for a registraless account, a value for the contact address
     * custom parameter otherwise. This will help the dispatching of incoming
     * requests between accounts with the same username. For address-of-record
     * user@example.com, the returned value woud be example_com.
     *
     * @return null for a registraless account, a value for the
     * "registering_acc" contact address parameter otherwise
     */
    public String getContactAddressCustomParamValue()
    {
            SipRegistrarConnection src = getRegistrarConnection();
            if (src != null && !src.isRegistrarless())
            {
                // if we don't replace the dots in the hostname, we get
                // "476 No Server Address in Contacts Allowed"
                // from certain registrars (ippi.fr for instance)
                String hostValue = ((SipURI) src.getAddressOfRecord().getURI())
                    .getHost().replace('.', '_');
                return hostValue;
            }
            return null;
    }

    /**
     * Returns the AddressFactory used to create URLs ans Address objects.
     *
     * @return the AddressFactory used to create URLs ans Address objects.
     */
    public AddressFactory getAddressFactory()
    {
        return addressFactory;
    }

    /**
     * Returns the HeaderFactory used to create SIP message headers.
     *
     * @return the HeaderFactory used to create SIP message headers.
     */
    public HeaderFactory getHeaderFactory()
    {
        return headerFactory;
    }

    /**
     * Returns the Message Factory used to create SIP messages.
     *
     * @return the Message Factory used to create SIP messages.
     */
    public SipMessageFactory getMessageFactory()
    {
        if (messageFactory == null)
        {
            messageFactory =
                new SipMessageFactory(this, new MessageFactoryImpl());
        }
        return messageFactory;
    }

    /**
     * Returns all running instances of ProtocolProviderServiceSipImpl
     *
     * @return all running instances of ProtocolProviderServiceSipImpl
     */
    public static Set<ProtocolProviderServiceSipImpl> getAllInstances()
    {
        try
        {
            Set<ProtocolProviderServiceSipImpl> instances
                = new HashSet<ProtocolProviderServiceSipImpl>();
            BundleContext context = SipActivator.getBundleContext();
            ServiceReference[] references = context.getServiceReferences(
                    ProtocolProviderService.class.getName(),
                    null
                    );
            for(ServiceReference reference : references)
            {
                Object service = context.getService(reference);
                if(service instanceof ProtocolProviderServiceSipImpl)
                    instances.add((ProtocolProviderServiceSipImpl) service);
            }
            return instances;
        }
        catch(InvalidSyntaxException ex)
        {
            if (logger.isDebugEnabled())
                logger.debug("Problem parcing an osgi expression", ex);
            // should never happen so crash if it ever happens
            throw new RuntimeException(
                    "getServiceReferences() wasn't supposed to fail!"
                    );
        }
     }

    /**
     * Returns the default listening point that we use for communication over
     * <tt>transport</tt>.
     *
     * @param transport the transport that the returned listening point needs
     * to support.
     *
     * @return the default listening point that we use for communication over
     * <tt>transport</tt> or null if no such transport is supported.
     */
    public ListeningPoint getListeningPoint(String transport)
    {
        if(logger.isTraceEnabled())
            logger.trace("Query for a " + transport + " listening point");

        //override the transport in case we have an outbound proxy.
        if(getOutboundProxy() != null)
        {
            if (logger.isTraceEnabled())
                logger.trace("Will use proxy address");
            transport = outboundProxyTransport;
        }

        if(   transport == null
           || transport.trim().length() == 0
           || (   ! transport.trim().equalsIgnoreCase(ListeningPoint.TCP)
               && ! transport.trim().equalsIgnoreCase(ListeningPoint.UDP)
               && ! transport.trim().equalsIgnoreCase(ListeningPoint.TLS)))
        {
            transport = getDefaultTransport();
        }

        ListeningPoint lp = null;

        if(transport.equalsIgnoreCase(ListeningPoint.UDP))
        {
            lp = sipStackSharing.getLP(ListeningPoint.UDP);
        }
        else if(transport.equalsIgnoreCase(ListeningPoint.TCP))
        {
            lp = sipStackSharing.getLP(ListeningPoint.TCP);
        }
        else if(transport.equalsIgnoreCase(ListeningPoint.TLS))
        {
            lp = sipStackSharing.getLP(ListeningPoint.TLS);
        }

        if(logger.isTraceEnabled())
        {
            logger.trace("Returning LP " + lp + " for transport ["
                            + transport + "] and ");
        }
        return lp;
    }

    /**
     * Returns the default listening point that we should use to contact the
     * intended destination.
     *
     * @param intendedDestination the address that we will be trying to contact
     * through the listening point we are trying to obtain.
     *
     * @return the listening point that we should use to contact the
     * intended destination.
     */
    public ListeningPoint getListeningPoint(Address intendedDestination)
    {
        return getListeningPoint((SipURI)intendedDestination.getURI());
    }

    /**
     * Returns the default listening point that we should use to contact the
     * intended destination.
     *
     * @param intendedDestination the address that we will be trying to contact
     * through the listening point we are trying to obtain.
     *
     * @return the listening point that we should use to contact the
     * intended destination.
     */
    public ListeningPoint getListeningPoint(SipURI intendedDestination)
    {
        String transport = intendedDestination.getTransportParam();

        return getListeningPoint(transport);
    }

    /**
     * Returns the default jain sip provider that we use for communication over
     * <tt>transport</tt>.
     *
     * @param transport the transport that the returned provider needs
     * to support.
     *
     * @return the default jain sip provider that we use for communication over
     * <tt>transport</tt> or null if no such transport is supported.
     */
    public SipProvider getJainSipProvider(String transport)
    {
        return sipStackSharing.getJainSipProvider(transport);
    }

    /**
     * Reurns the currently valid sip security manager that everyone should
     * use to authenticate SIP Requests.
     * @return the currently valid instace of a SipSecurityManager that everyone
     * sould use to authenticate SIP Requests.
     */
    public SipSecurityManager getSipSecurityManager()
    {
        return sipSecurityManager;
    }

    /**
     * Initializes the SipRegistrarConnection that this class will be using.
     *
     * @param accountID the ID of the account that this registrar is associated
     * with.
     * @throws java.lang.IllegalArgumentException if one or more account
     * properties have invalid values.
     */
    private void initRegistrarConnection(SipAccountID accountID)
        throws IllegalArgumentException
    {
        //First init the registrar address
        String registrarAddressStr =
            accountID
                .getAccountPropertyString(ProtocolProviderFactory.SERVER_ADDRESS);

        //if there is no registrar address, parse the user_id and extract it
        //from the domain part of the SIP URI.
        if (registrarAddressStr == null)
        {
            String userID =
                accountID
                    .getAccountPropertyString(ProtocolProviderFactory.USER_ID);
            int index = userID.indexOf('@');
            if ( index > -1 )
                registrarAddressStr = userID.substring( index+1);
        }

        //if we still have no registrar address or if the registrar address
        //string is one of our local host addresses this means the users does
        //not want to use a registrar connection
        if(registrarAddressStr == null
           || registrarAddressStr.trim().length() == 0)
        {
            initRegistrarlessConnection(accountID);
            return;
        }

        //from this point on we are certain to have a registrar.
        InetSocketAddress[] registrarSocketAddresses = null;

        //registrar transport
        String registrarTransport = accountID.getAccountPropertyString(
                ProtocolProviderFactory.PREFERRED_TRANSPORT);

        if(registrarTransport != null && registrarTransport.length() > 0)
        {
            if( ! registrarTransport.equals(ListeningPoint.UDP)
                && !registrarTransport.equals(ListeningPoint.TCP)
                && !registrarTransport.equals(ListeningPoint.TLS))
            {
                throw new IllegalArgumentException(registrarTransport
                    + " is not a valid transport protocol. Transport must be "
                    +"left blanc or set to TCP, UDP or TLS.");
            }
        }
        else
        {
            registrarTransport = getDefaultTransport();
        }

        //init registrar port
        int registrarPort = ListeningPoint.PORT_5060;

        try
        {
            // if port is set we must use the explicitly set settings and
            // skip SRV queries
            if(accountID.getAccountProperty(
                    ProtocolProviderFactory.SERVER_PORT) != null)
            {
                ArrayList<InetSocketAddress> registrarSocketAddressesList =
                        new ArrayList<InetSocketAddress>();

                // get only AAAA and A records
                resolveAddresses(
                        registrarAddressStr,
                        registrarSocketAddressesList,
                        checkPreferIPv6Addresses(),
                        registrarPort
                );
                registrarSocketAddresses = registrarSocketAddressesList
                        .toArray(new InetSocketAddress[0]);
            }
            else
            {
                ArrayList<InetSocketAddress> registrarSocketAddressesList =
                        new ArrayList<InetSocketAddress>();
                ArrayList<String> registrarTransports =
                        new ArrayList<String>();

                resolveSipAddress(
                    registrarAddressStr,
                    registrarTransport,
                    registrarSocketAddressesList,
                    registrarTransports,
                    accountID.getAccountPropertyBoolean(
                        ProtocolProviderFactory.PROXY_AUTO_CONFIG, false));

                registrarTransport = registrarTransports.get(0);
                registrarSocketAddresses = registrarSocketAddressesList
                        .toArray(new InetSocketAddress[0]);
            }

            // We should set here the property to indicate that the server
            // address is validated. When we load stored accounts we check
            // this property in order to prevent checking again the server
            // address. And this is needed because in the case we don't have
            // network while loading the application we still want to have our
            // accounts loaded.
            if(registrarSocketAddresses != null
                    && registrarSocketAddresses.length > 0)
                accountID.putAccountProperty(
                    ProtocolProviderFactory.SERVER_ADDRESS_VALIDATED,
                    Boolean.toString(true));
        }
        catch (UnknownHostException ex)
        {
            if (logger.isDebugEnabled())
                logger.debug(registrarAddressStr
                        + " appears to be an either invalid"
                        + " or inaccessible address.",
                        ex);

            boolean isServerValidated =
                accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.SERVER_ADDRESS_VALIDATED, false);

            /*
             * We should check here if the server address was already validated.
             * When we load stored accounts we want to prevent checking again
             * the server address. This is needed because in the case we don't
             * have network while loading the application we still want to have
             * our accounts loaded.
             */
            if (!isServerValidated)
            {
                throw new IllegalArgumentException(
                    registrarAddressStr
                    + " appears to be an either invalid"
                    + " or inaccessible address.",
                    ex);
            }
        }

        // If the registrar address is null we don't need to continue.
        // If we still have problems with initializing the registrar we are
        // telling the user. We'll enter here only if the server has been
        // already validated (this means that the account is already created
        // and we're trying to login, but we have no internet connection).
        if(registrarSocketAddresses == null
            || registrarSocketAddresses.length == 0)
        {
            fireRegistrationStateChanged(
                RegistrationState.UNREGISTERED,
                RegistrationState.CONNECTION_FAILED,
                RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
                "Invalid or inaccessible server address.");

            return;
        }

        // check if user has overridden the registrar port.
        registrarPort =
            accountID.getAccountPropertyInt(
                ProtocolProviderFactory.SERVER_PORT, registrarPort);
        if (registrarPort > NetworkUtils.MAX_PORT_NUMBER)
        {
            throw new IllegalArgumentException(registrarPort
                + " is larger than " + NetworkUtils.MAX_PORT_NUMBER
                + " and does not therefore represent a valid port number.");
        }

        //init expiration timeout
        int expires =
            SipActivator.getConfigurationService().getInt(
                REGISTRATION_EXPIRATION,
                SipRegistrarConnection.DEFAULT_REGISTRATION_EXPIRATION);

        //Initialize our connection with the registrar
        try
        {
            this.sipRegistrarConnection = new SipRegistrarConnection(
                registrarSocketAddresses
                , registrarTransport
                , expires
                , this);
        }
        catch (ParseException ex)
        {
            //this really shouldn't happen as we're using InetAddress-es
            logger.error("Failed to create a registrar connection with "
                +registrarSocketAddresses[0].getAddress().getHostAddress()
                , ex);
            throw new IllegalArgumentException(
                "Failed to create a registrar connection with "
                + registrarSocketAddresses[0].getAddress().getHostAddress() + ": "
                + ex.getMessage());
        }
    }

    /**
     * Initializes the SipRegistrarConnection that this class will be using.
     *
     * @param accountID the ID of the account that this registrar is associated
     * with.
     * @throws java.lang.IllegalArgumentException if one or more account
     * properties have invalid values.
     */
    private void initRegistrarlessConnection(SipAccountID accountID)
        throws IllegalArgumentException
    {
        //registrar transport
        String registrarTransport =
            accountID
                .getAccountPropertyString(ProtocolProviderFactory.PREFERRED_TRANSPORT);

        if(registrarTransport != null && registrarTransport.length() > 0)
        {
            if( ! registrarTransport.equals(ListeningPoint.UDP)
                && !registrarTransport.equals(ListeningPoint.TCP)
                && !registrarTransport.equals(ListeningPoint.TLS))
            {
                throw new IllegalArgumentException(registrarTransport
                    + " is not a valid transport protocol. Transport must be "
                    +"left blanc or set to TCP, UDP or TLS.");
            }
        }
        else
        {
            registrarTransport = ListeningPoint.UDP;
        }

        //Initialize our connection with the registrar
        this.sipRegistrarConnection
               = new SipRegistrarlessConnection(this, registrarTransport);
    }

    /**
     * Returns the SIP address of record (Display Name <user@server.net>) that
     * this account is created for. The method takes into account whether or
     * not we are running in Registar or "No Registar" mode and either returns
     * the AOR we are using to register or an address constructed using the
     * local address.
     *
     * @param intendedDestination the destination that we would be using the
     * local address to communicate with.
     *
     * @return our Address Of Record that we should use in From headers.
     */
    public Address getOurSipAddress(Address intendedDestination)
    {
        return getOurSipAddress((SipURI)intendedDestination.getURI());
    }

    /**
     * Returns the SIP address of record (Display Name <user@server.net>) that
     * this account is created for. The method takes into account whether or
     * not we are running in Registar or "No Registar" mode and either returns
     * the AOR we are using to register or an address constructed using the
     * local address
     *
     * @param intendedDestination the destination that we would be using the
     * local address to communicate with.
     * .
     * @return our Address Of Record that we should use in From headers.
     */
    public Address getOurSipAddress(SipURI intendedDestination)
    {
        SipRegistrarConnection src = getRegistrarConnection();

        if( src != null && !src.isRegistrarless() )
            return src.getAddressOfRecord();

        //we are apparently running in "No Registrar" mode so let's create an
        //address by ourselves.
        InetSocketAddress destinationAddr
                    = getIntendedDestination(intendedDestination);

        InetAddress localHost = SipActivator.getNetworkAddressManagerService()
            .getLocalHost(destinationAddr.getAddress());

        String userID = getAccountID().getUserID();

        try
        {
            SipURI ourSipURI = getAddressFactory()
                .createSipURI(userID, localHost.getHostAddress());

            ListeningPoint lp = getListeningPoint(intendedDestination);

            ourSipURI.setTransportParam(lp.getTransport());
            ourSipURI.setPort(lp.getPort());

            Address ourSipAddress = getAddressFactory()
                .createAddress(getOurDisplayName(), ourSipURI);

            ourSipAddress.setDisplayName(getOurDisplayName());

            return ourSipAddress;
        }
        catch (ParseException exc)
        {
            if (logger.isTraceEnabled())
                logger.trace("Failed to create our SIP AOR address", exc);
            // this should never happen since we are using InetAddresses
            // everywhere so parsing could hardly go wrong.
            throw new IllegalArgumentException(
                            "Failed to create our SIP AOR address"
                            , exc);
        }
    }

    /**
     * In case we are using an outbound proxy this method returns
     * a suitable string for use with Router.
     * The method returns <tt>null</tt> otherwise.
     *
     * @return the string of our outbound proxy if we are using one and
     * <tt>null</tt> otherwise.
     */
    public String getOutboundProxyString()
    {
        return this.outboundProxyString;
    }

    /**
     * In case we are using an outbound proxy this method returns its address.
     * The method returns <tt>null</tt> otherwise.
     *
     * @return the address of our outbound proxy if we are using one and
     * <tt>null</tt> otherwise.
     */
    public InetSocketAddress getOutboundProxy()
    {
        return this.outboundProxySocketAddress;
    }

    /**
     * In case we are using an outbound proxy this method returns the transport
     * we are using to connect to it. The method returns <tt>null</tt>
     * otherwise.
     *
     * @return the transport used to connect to our outbound proxy if we are
     * using one and <tt>null</tt> otherwise.
     */
    public String getOutboundProxyTransport()
    {
        return this.outboundProxyTransport;
    }

    /**
     * Extracts all properties concerning the usage of an outbound proxy for
     * this account.
     * @param accountID the account whose outbound proxy we are currently
     * initializing.
     * @param ix index of the address to use.
     */
    void initOutboundProxy(SipAccountID accountID, int ix)
    {
        //First init the proxy address
        String proxyAddressStr =
            accountID
                .getAccountPropertyString(ProtocolProviderFactory.
                        PROXY_ADDRESS);
        boolean proxyAddressAndPortEntered = false;

        if(proxyAddressStr == null || proxyAddressStr.trim().length() == 0)
        {
            proxyAddressStr = accountID
                .getAccountPropertyString(ProtocolProviderFactory.
                        SERVER_ADDRESS);

            if(proxyAddressStr == null || proxyAddressStr.trim().length() == 0)
            {
                /* registrarless account */
                return;
            }
        }
        else
        {
            if(accountID.getAccountProperty(ProtocolProviderFactory.PROXY_PORT)
                    != null)
            {
                proxyAddressAndPortEntered = true;
            }
        }

        InetAddress proxyAddress = null;

        //init proxy port
        int proxyPort = ListeningPoint.PORT_5060;

        //proxy transport
        String proxyTransport = accountID.getAccountPropertyString(
                ProtocolProviderFactory.PREFERRED_TRANSPORT);

        if (proxyTransport != null && proxyTransport.length() > 0)
        {
            if (!proxyTransport.equals(ListeningPoint.UDP)
                && !proxyTransport.equals(ListeningPoint.TCP)
                && !proxyTransport.equals(ListeningPoint.TLS))
            {
                throw new IllegalArgumentException(proxyTransport
                    + " is not a valid transport protocol. Transport must be "
                    + "left blank or set to TCP, UDP or TLS.");
            }
        }
        else
        {
            proxyTransport = getDefaultTransport();
        }

        try
        {
            //check if user has overridden proxy port.
            proxyPort = accountID.getAccountPropertyInt(
                    ProtocolProviderFactory.PROXY_PORT,
                    proxyPort);
            if (proxyPort > NetworkUtils.MAX_PORT_NUMBER)
            {
                throw new IllegalArgumentException(proxyPort + " is larger than "
                    + NetworkUtils.MAX_PORT_NUMBER
                    + " and does not therefore represent a valid port number.");
            }

            InetSocketAddress proxySocketAddress = null;

            if(accountID.getAccountPropertyBoolean(
                ProtocolProviderFactory.PROXY_AUTO_CONFIG, false))
            {
                ArrayList<InetSocketAddress> proxySocketAddressesList
                        = new ArrayList<InetSocketAddress>();
                ArrayList<String> proxyTransportsList
                        = new ArrayList<String>();

                resolveSipAddress(
                        proxyAddressStr,
                        proxyTransport,
                        proxySocketAddressesList,
                        proxyTransportsList,
                        true);

                proxyTransport = proxyTransportsList.get(ix);
                proxySocketAddress = proxySocketAddressesList.get(ix);
            }
            else
            {
                // according rfc3263 if proxy address and port are
                // explicitly entered don't make SRV queries
                if(proxyAddressAndPortEntered)
                {
                    ArrayList<InetSocketAddress> addresses
                        = new ArrayList<InetSocketAddress>();

                    resolveAddresses(
                        proxyAddressStr,
                        addresses,
                        checkPreferIPv6Addresses(),
                        proxyPort);
                    // only set if enough results found
                    if(addresses.size() > ix)
                        proxySocketAddress = addresses.get(ix);
                }
                else
                {
                    ArrayList<InetSocketAddress> proxySocketAddressesList
                        = new ArrayList<InetSocketAddress>();
                    ArrayList<String> proxyTransportsList
                        = new ArrayList<String>();

                    resolveSipAddress(
                        proxyAddressStr,
                        proxyTransport,
                        proxySocketAddressesList,
                        proxyTransportsList,
                        false);

                    proxyTransport = proxyTransportsList.get(ix);
                    proxySocketAddress = proxySocketAddressesList.get(ix);
                }
            }

            if(proxySocketAddress == null)
                throw new UnknownHostException();

            proxyAddress = proxySocketAddress.getAddress();
            proxyPort = proxySocketAddress.getPort();

            if (logger.isTraceEnabled())
                logger.trace("Setting proxy address = " + proxyAddressStr);

            // We should set here the property to indicate that the proxy
            // address is validated. When we load stored accounts we check
            // this property in order to prevent checking again the proxy
            // address. this is needed because in the case we don't have
            // network while loading the application we still want to have
            // our accounts loaded.
            accountID.putAccountProperty(
                ProtocolProviderFactory.PROXY_ADDRESS_VALIDATED,
                Boolean.toString(true));
        }
        catch (UnknownHostException ex)
        {
            logger.error(proxyAddressStr
                            + " appears to be an either invalid"
                            + " or inaccessible address.",
                            ex);

            boolean isProxyValidated =
                accountID.getAccountPropertyBoolean(
                    ProtocolProviderFactory.PROXY_ADDRESS_VALIDATED, false);

            // We should check here if the proxy address was already validated.
            // When we load stored accounts we want to prevent checking again the
            // proxy address. This is needed because in the case we don't have
            // network while loading the application we still want to have our
            // accounts loaded.
            if (!isProxyValidated)
            {
                throw new IllegalArgumentException(
                    proxyAddressStr
                    + " appears to be an either invalid or"
                    + " inaccessible address.",
                    ex);
            }
        }

        // Return if no proxy is specified or if the proxyAddress is null.
        if(proxyAddress == null)
        {
            return;
        }

        StringBuilder proxyStringBuffer
            = new StringBuilder(proxyAddress.getHostAddress());

        if(proxyAddress instanceof Inet6Address)
        {
            proxyStringBuffer.insert(0, '[');
            proxyStringBuffer.append(']');
        }

        proxyStringBuffer.append(':');
        proxyStringBuffer.append(Integer.toString(proxyPort));
        proxyStringBuffer.append('/');
        proxyStringBuffer.append(proxyTransport);

        //done parsing. init properties.
        this.outboundProxyString = proxyStringBuffer.toString();

        //store a reference to our sip proxy so that we can use it when
        //constructing via and contact headers.
        this.outboundProxySocketAddress
            = new InetSocketAddress(proxyAddress, proxyPort);
        this.outboundProxyTransport = proxyTransport;
    }

    /**
     * Registers <tt>methodProcessor</tt> in the <tt>methorProcessors</tt> table
     * so that it would receives all messages in a transaction initiated by a
     * <tt>method</tt> request. If any previous processors exist for the same
     * method, they will be replaced by this one.
     *
     * @param method a String representing the SIP method that we're registering
     *            the processor for (e.g. INVITE, REGISTER, or SUBSCRIBE).
     * @param methodProcessor a <tt>MethodProcessor</tt> implementation that
     *            would handle all messages received within a <tt>method</tt>
     *            transaction.
     */
    public void registerMethodProcessor(String method,
        MethodProcessor methodProcessor)
    {
        List<MethodProcessor> processors = methodProcessors.get(method);
        if (processors == null)
        {
            processors = new LinkedList<MethodProcessor>();
            methodProcessors.put(method, processors);
        }
        else
        {
            /*
             * Prevent the registering of multiple instances of one and the same
             * OperationSet class and take only the latest registration into
             * account.
             */
            Iterator<MethodProcessor> processorIter = processors.iterator();
            Class<? extends MethodProcessor> methodProcessorClass
                = methodProcessor.getClass();
            /*
             * EventPackageSupport and its extenders provide a generic mechanizm
             * for building support for a specific event package so allow them
             * to register multiple instances of one and the same class as long
             * as they are handling different event packages.
             */
            String eventPackage
                = (methodProcessor instanceof EventPackageSupport)
                    ? ((EventPackageSupport) methodProcessor).getEventPackage()
                    : null;

            while (processorIter.hasNext())
            {
                MethodProcessor processor = processorIter.next();

                if (!processor.getClass().equals(methodProcessorClass))
                    continue;
                if ((eventPackage != null)
                        && (processor instanceof EventPackageSupport)
                        && !eventPackage
                                .equals(
                                    ((EventPackageSupport) processor)
                                        .getEventPackage()))
                    continue;

                processorIter.remove();
            }
        }
        processors.add(methodProcessor);
    }

    /**
     * Unregisters <tt>methodProcessor</tt> from the <tt>methorProcessors</tt>
     * table so that it won't receive further messages in a transaction
     * initiated by a <tt>method</tt> request.
     *
     * @param method the name of the method whose processor we'd like to
     * unregister.
     * @param methodProcessor the <tt>MethodProcessor</tt> that we'd like to
     * unregister.
     */
    public void unregisterMethodProcessor(String method,
        MethodProcessor methodProcessor)
    {
        List<MethodProcessor> processors = methodProcessors.get(method);
        if ((processors != null) && processors.remove(methodProcessor)
            && (processors.size() <= 0))
        {
            methodProcessors.remove(method);
        }
    }

    /**
     * Returns the transport that we should use if we have no clear idea of our
     * destination's preferred transport. The method would first check if
     * we are running behind an outbound proxy and if so return its transport.
     * If no outbound proxy is set, the method would check the contents of the
     * DEFAULT_TRANSPORT property and return it if not null. Otherwise the
     * method would return UDP;
     *
     * @return The first non null password of the following:
     * a) the transport we use to communicate with our registrar
     * b) the transport of our outbound proxy,
     * c) the transport specified by the DEFAULT_TRANSPORT property, UDP.
     */
    public String getDefaultTransport()
    {
        SipRegistrarConnection srConnection = getRegistrarConnection();

        if( srConnection != null)
        {
            String registrarTransport = srConnection.getTransport();
            if(   registrarTransport != null
               && registrarTransport.length() > 0)
           {
               return registrarTransport;
           }
        }

        if(outboundProxySocketAddress != null
            && outboundProxyTransport != null)
        {
            return outboundProxyTransport;
        }
        else
        {
            String userSpecifiedDefaultTransport
                = SipActivator.getConfigurationService()
                    .getString(DEFAULT_TRANSPORT);

            if(userSpecifiedDefaultTransport != null)
            {
                return userSpecifiedDefaultTransport;
            }
            else
            {
                String defTransportDefaultValue = SipActivator.getResources()
                        .getSettingsString(DEFAULT_TRANSPORT);

                if(!StringUtils.isNullOrEmpty(defTransportDefaultValue))
                    return defTransportDefaultValue;
                else
                    return ListeningPoint.UDP;
            }
        }
    }

    /**
     * Returns the provider that corresponds to the transport returned by
     * getDefaultTransport(). Equivalent to calling
     * getJainSipProvider(getDefaultTransport())
     *
     * @return the Jain SipProvider that corresponds to the transport returned
     * by getDefaultTransport().
     */
    public SipProvider getDefaultJainSipProvider()
    {
        return getJainSipProvider(getDefaultTransport());
    }

    /**
     * Returns the listening point that corresponds to the transport returned by
     * getDefaultTransport(). Equivalent to calling
     * getListeningPoint(getDefaultTransport())
     *
     * @return the Jain SipProvider that corresponds to the transport returned
     * by getDefaultTransport().
     */
    public ListeningPoint getDefaultListeningPoint()
    {
        return getListeningPoint(getDefaultTransport());
    }

    /**
     * Returns the display name string that the user has set as a display name
     * for this account.
     *
     * @return the display name string that the user has set as a display name
     * for this account.
     */
    public String getOurDisplayName()
    {
        return ourDisplayName;
    }

    /**
     * Changes the display name string.
     *
     * @return whether we have successfully changed the display name.
     */
    boolean setOurDisplayName(String newDisplayName)
    {
        // if we really want to change the display name
        // and it is existing, change it.
        if(newDisplayName != null && !ourDisplayName.equals(newDisplayName))
        {
            getAccountID().putAccountProperty(
                    ProtocolProviderFactory.DISPLAY_NAME,
                    newDisplayName);

            ourDisplayName = newDisplayName;

            OperationSetServerStoredAccountInfoSipImpl accountInfoOpSet
                = (OperationSetServerStoredAccountInfoSipImpl)getOperationSet(
                    OperationSetServerStoredAccountInfo.class);

            if(accountInfoOpSet != null)
                accountInfoOpSet.setOurDisplayName(newDisplayName);

            return true;
        }

        return false;
    }

    /**
     * Returns a User Agent header that could be used for signing our requests.
     *
     * @return a <tt>UserAgentHeader</tt> that could be used for signing our
     * requests.
     */
    public UserAgentHeader getSipCommUserAgentHeader()
    {
        if(userAgentHeader == null)
        {
            try
            {
                List<String> userAgentTokens = new LinkedList<String>();

                Version ver =
                        SipActivator.getVersionService().getCurrentVersion();

                userAgentTokens.add(ver.getApplicationName());
                userAgentTokens.add(ver.toString());

                String osName = System.getProperty("os.name");
                userAgentTokens.add(osName);

                userAgentHeader
                    = this.headerFactory.createUserAgentHeader(userAgentTokens);
            }
            catch (ParseException ex)
            {
                //shouldn't happen
                return null;
            }
        }
        return userAgentHeader;
    }

    /**
     * Send an error response with the <tt>errorCode</tt> code using
     * <tt>serverTransaction</tt> and do not surface exceptions. The method
     * is useful when we are sending the error response in a stack initiated
     * operation and don't have the possibility to escalate potential
     * exceptions, so we can only log them.
     *
     * @param serverTransaction the transaction that we'd like to send an error
     * response in.
     * @param errorCode the code that the response should have.
     */
    public void sayErrorSilently(ServerTransaction serverTransaction,
                                 int               errorCode)
    {
        try
        {
            sayError(serverTransaction, errorCode);
        }
        catch (OperationFailedException exc)
        {
            if (logger.isDebugEnabled())
                logger.debug("Failed to send an error " + errorCode + " response",
                        exc);
        }
    }

    /**
     * Sends an ACK request in the specified dialog.
     *
     * @param clientTransaction the transaction that resulted in the ACK we are
     * about to send (MUST be an INVITE transaction).
     *
     * @throws InvalidArgumentException if there is a problem with the supplied
     * CSeq ( for example <= 0 ).
     * @throws SipException if the CSeq does not relate to a previously sent
     * INVITE or if the Method that created the Dialog is not an INVITE ( for
     * example SUBSCRIBE) or if we fail to send the INVITE for whatever reason.
     */
    public void sendAck(ClientTransaction clientTransaction)
        throws SipException, InvalidArgumentException
    {
            Request ack = messageFactory.createAck(clientTransaction);

            clientTransaction.getDialog().sendAck(ack);
    }

    /**
     * Send an error response with the <tt>errorCode</tt> code using
     * <tt>serverTransaction</tt>.
     *
     * @param serverTransaction the transaction that we'd like to send an error
     * response in.
     * @param errorCode the code that the response should have.
     *
     * @throws OperationFailedException if we failed constructing or sending a
     * SIP Message.
     */
    public void sayError(ServerTransaction serverTransaction, int errorCode)
        throws OperationFailedException
    {
        Request request = serverTransaction.getRequest();
        Response errorResponse = null;
        try
        {
            errorResponse = getMessageFactory().createResponse(
                            errorCode, request);

            //we used to be adding a To tag here and we shouldn't. 3261 says:
            //"Dialogs are created through [...] non-failure responses". and
            //we are using this method for failure responses only.

        }
        catch (ParseException ex)
        {
            ProtocolProviderServiceSipImpl.throwOperationFailedException(
                "Failed to construct an OK response to an INVITE request",
                OperationFailedException.INTERNAL_ERROR, ex, logger);
        }

        try
        {
            serverTransaction.sendResponse(errorResponse);
            if (logger.isDebugEnabled())
                logger.debug("sent response: " + errorResponse);
        }
        catch (Exception ex)
        {
            ProtocolProviderServiceSipImpl.throwOperationFailedException(
                "Failed to send an OK response to an INVITE request",
                OperationFailedException.INTERNAL_ERROR, ex, logger);
        }
    }

    /**
     * Sends a specific <tt>Request</tt> through a given
     * <tt>SipProvider</tt> as part of the conversation associated with a
     * specific <tt>Dialog</tt>.
     *
     * @param sipProvider the <tt>SipProvider</tt> to send the specified
     * request through
     * @param request the <tt>Request</tt> to send through
     * <tt>sipProvider</tt>
     * @param dialog the <tt>Dialog</tt> as part of which the specified
     * <tt>request</tt> is to be sent
     *
     * @throws OperationFailedException if creating a transaction or sending
     * the <tt>request</tt> fails.
     */
    public void sendInDialogRequest(SipProvider sipProvider,
                                    Request request,
                                    Dialog dialog)
        throws OperationFailedException
    {
        ClientTransaction clientTransaction = null;
        try
        {
            clientTransaction = sipProvider.getNewClientTransaction(request);
        }
        catch (TransactionUnavailableException ex)
        {
            ProtocolProviderServiceSipImpl.throwOperationFailedException(
                "Failed to create a client transaction for request:\n"
                + request, OperationFailedException.INTERNAL_ERROR, ex, logger);
        }

        try
        {
            dialog.sendRequest(clientTransaction);
        }
        catch (SipException ex)
        {
            ProtocolProviderServiceSipImpl.throwOperationFailedException(
                "Failed to send request:\n" + request,
                OperationFailedException.NETWORK_FAILURE,
                ex,
                logger);
        }

        if (logger.isDebugEnabled())
            logger.debug("Sent request:\n" + request);
    }

    /**
     * Returns a List of Strings corresponding to all methods that we have a
     * processor for.
     *
     * @return a List of methods that we support.
     */
    public List<String> getSupportedMethods()
    {
        return new ArrayList<String>(methodProcessors.keySet());
    }

    /**
     * A utility class that allows us to block until we receive a
     * <tt>RegistrationStateChangeEvent</tt> notifying us of an unregistration.
     */
    private static class ShutdownUnregistrationBlockListener
        implements RegistrationStateChangeListener
    {
        /**
         * The list where we store <tt>RegistationState</tt>s received while
         * waiting.
         */
        public List<RegistrationState> collectedNewStates =
                                        new LinkedList<RegistrationState>();

        /**
         * The method would simply register all received events so that they
         * could be available for later inspection by the unit tests. In the
         * case where a registration event notifying us of a completed
         * registration is seen, the method would call notifyAll().
         *
         * @param evt ProviderStatusChangeEvent the event describing the
         * status change.
         */
        public void registrationStateChanged(RegistrationStateChangeEvent evt)
        {
            if (logger.isDebugEnabled())
                logger.debug("Received a RegistrationStateChangeEvent: " + evt);

            collectedNewStates.add(evt.getNewState());

            if (evt.getNewState().equals(RegistrationState.UNREGISTERED))
            {
                if (logger.isDebugEnabled())
                    logger.debug(
                            "We're unregistered and will notify those who wait");
                synchronized (this)
                {
                    notifyAll();
                }
            }
        }

        /**
         * Blocks until an event notifying us of the awaited state change is
         * received or until waitFor milliseconds pass (whichever happens first).
         *
         * @param waitFor the number of milliseconds that we should be waiting
         * for an event before simply bailing out.
         */
        public void waitForEvent(long waitFor)
        {
            if (logger.isTraceEnabled())
                logger.trace("Waiting for a "
                        +"RegistrationStateChangeEvent.UNREGISTERED");

            synchronized (this)
            {
                if (collectedNewStates.contains(
                        RegistrationState.UNREGISTERED))
                {
                    if (logger.isTraceEnabled())
                        logger.trace("Event already received. "
                                + collectedNewStates);
                    return;
                }

                try
                {
                    wait(waitFor);

                    if (collectedNewStates.size() > 0)
                        if (logger.isTraceEnabled())
                            logger.trace(
                                    "Received a RegistrationStateChangeEvent.");
                    else
                        if (logger.isTraceEnabled())
                            logger.trace(
                                    "No RegistrationStateChangeEvent received for "
                                    + waitFor + "ms.");

                }
                catch (InterruptedException ex)
                {
                    if (logger.isDebugEnabled())
                        logger.debug(
                                "Interrupted while waiting for a "
                                +"RegistrationStateChangeEvent"
                                , ex);
                }
            }
        }
    }

    /**
     * Returns the sip protocol icon.
     * @return the sip protocol icon
     */
    public ProtocolIcon getProtocolIcon()
    {
        return protocolIcon;
    }

    /**
     * Returns the current instance of <tt>SipStatusEnum</tt>.
     *
     * @return the current instance of <tt>SipStatusEnum</tt>.
     */
    SipStatusEnum getSipStatusEnum()
    {
        return sipStatusEnum;
    }
    /**
     * Returns the current instance of <tt>SipRegistrarConnection</tt>.
     * @return SipRegistrarConnection
     */
    SipRegistrarConnection getRegistrarConnection()
    {
        return sipRegistrarConnection;
    }

    /**
     * Parses the the <tt>uriStr</tt> string and returns a JAIN SIP URI.
     *
     * @param uriStr a <tt>String</tt> containing the uri to parse.
     *
     * @return a URI object corresponding to the <tt>uriStr</tt> string.
     * @throws ParseException if uriStr is not properly formatted.
     */
    public Address parseAddressString(String uriStr)
        throws ParseException
    {
        uriStr = uriStr.trim();

        //we don't know how to handle the "tel:" scheme ... or rather we handle
        //it same as sip so replace:
        if(uriStr.toLowerCase().startsWith("tel:"))
            uriStr = "sip:" + uriStr.substring("tel:".length());

        //Handle default domain name (i.e. transform 1234 -> 1234@sip.com)
        //assuming that if no domain name is specified then it should be the
        //same as ours.
        if (uriStr.indexOf('@') == -1)
        {
            //if we have a registrar, then we could append its domain name as
            //default
            SipRegistrarConnection src = getRegistrarConnection();
            if(src != null && !src.isRegistrarless() )
            {
                uriStr = uriStr + "@"
                    + ((SipURI)src.getAddressOfRecord().getURI()).getHost();
            }

            //else this could only be a host ... but this should work as is.
        }

        //Let's be uri fault tolerant and add the sip: scheme if there is none.
        if (!uriStr.toLowerCase().startsWith("sip:")) //no sip scheme
        {
            uriStr = "sip:" + uriStr;
        }

        Address toAddress = getAddressFactory().createAddress(uriStr);

        return toAddress;
    }

    /**
     * Tries to resolve <tt>address</tt> into a valid InetSocketAddress using
     * an <tt>SRV</tt> query where it exists and A/AAAA where it doesn't.
     * If there is no SRV,A or AAAA records return the socket address created
     * with the supplied <tt>address</tt>, so we can keep old behaviour.
     * When letting underling libs and java to resolve the address.
     *
     * @param address the address we'd like to resolve.
     * @param transport the protocol that we'd like to use when accessing
     * address.
     * @param resultAddresses the list that will be filled with the result
     * addresses. An <tt>InetSocketAddress</tt> instances containing the
     * <tt>SRV</tt> record for <tt>address</tt> if one has been defined and the
     * A/AAAA record where it hasn't.
     * @param resultTransports the transports for the <tt>resultAddresses</tt>.
     * @param resolveProtocol if the protocol should be resolved
     * @return the transports that is used, when the <tt>transport</tt> is with
     * value Auto we resolve the address with NAPTR query, if no NAPTR record
     * we will use the default one.
     *
     * @throws UnknownHostException if <tt>address</tt> is not a valid host
     * address.
     */
    public void resolveSipAddress(
            String address, String transport,
            List<InetSocketAddress> resultAddresses,
            List<String> resultTransports,
            boolean resolveProtocol)
        throws UnknownHostException
    {
        //we need to resolve the address only if its a hostname.
        if(NetworkUtils.isValidIPAddress(address))
        {
            InetAddress addressObj = NetworkUtils.getInetAddress(address);

            //this is an ip address so we need to return default ports since
            //we can't get them from a DNS.
            int port = ListeningPoint.PORT_5060;
            if(transport.equalsIgnoreCase(ListeningPoint.TLS))
                port = ListeningPoint.PORT_5061;

            resultTransports.add(transport);
            resultAddresses.add(new InetSocketAddress(addressObj, port));

            // as its ip address return, no dns is needed.
            return;
        }

        boolean preferIPv6Addresses = checkPreferIPv6Addresses();

        // first make NAPTR resolve to get protocol, if needed
        if(resolveProtocol)
        {
            try
            {
                String[][] naptrRecords = NetworkUtils.getNAPTRRecords(address);

                if(naptrRecords != null && naptrRecords.length > 0)
                {
                    for(String[] rec : naptrRecords)
                    {
                        resolveSRV(
                                rec[2],
                                rec[1],
                                resultAddresses,
                                resultTransports,
                                preferIPv6Addresses,
                                true);
                    }

                    // NAPTR found use only it
                    if(logger.isInfoEnabled())
                        logger.info("Found NAPRT record and using it:"
                            + resultAddresses);

                    // return only if found something
                    if(resultAddresses.size() > 0
                        && resultTransports.size() > 0)
                    return;
                }
            }
            catch (ParseException e)
            {
                logger.error("Error parsing dns record.", e);
            }
        }

        //try to obtain SRV mappings from the DNS
        try
        {
            resolveSRV(
                address,
                transport,
                resultAddresses,
                resultTransports,
                preferIPv6Addresses,
                false);
        }
        catch (ParseException e)
        {
            logger.error("Error parsing dns record.", e);
        }

        //no SRV means default ports
        int defaultPort = ListeningPoint.PORT_5060;
        if(transport.equalsIgnoreCase(ListeningPoint.TLS))
            defaultPort = ListeningPoint.PORT_5061;

        ArrayList<InetSocketAddress> tempResultAddresses
            = new ArrayList<InetSocketAddress>();

        // after checking SRVs, lets add and AAAA and A records
        resolveAddresses(
                address,
                tempResultAddresses,
                preferIPv6Addresses,
                defaultPort);
        for(InetSocketAddress a : tempResultAddresses)
        {
            if(!resultAddresses.contains(a))
            {
                resultAddresses.add(a);
                resultTransports.add(transport);
            }
        }

        // make sure we don't return empty array
        if(resultAddresses.size() == 0)
        {
            resultAddresses.add(new InetSocketAddress(address, defaultPort));
            resultTransports.add(transport);

            // there were no SRV mappings so we only need to A/AAAA resolve the
            // address. Do this before we instantiate the
            // resulting InetSocketAddress because its constructor
            // suppresses UnknownHostException-s and we want to know if
            // something goes wrong.
            InetAddress addressObj = InetAddress.getByName(address);
        }
    }

    /**
     * Resolves the given address. Resolves A and AAAA records and returns
     * them in <tt>resultAddresses</tt> ordered according
     * <tt>preferIPv6Addresses</tt> option.
     * @param address the address to resolve.
     * @param resultAddresses the List in which we provide the result.
     * @param preferIPv6Addresses whether ipv6 address should go before ipv4.
     * @param defaultPort the port to use for the result address.
     * @throws UnknownHostException its not supposed to be thrown, cause
     * the address we use is an ip address.
     */
    private void resolveAddresses(
            String address, List<InetSocketAddress> resultAddresses,
            boolean preferIPv6Addresses, int defaultPort)
            throws UnknownHostException
    {
        //we need to resolve the address only if its a hostname.
        if(NetworkUtils.isValidIPAddress(address))
        {
            InetAddress addressObj = NetworkUtils.getInetAddress(address);

            resultAddresses.add(new InetSocketAddress(addressObj, defaultPort));

            // as its ip address return, no dns is needed.
            return;
        }

        InetSocketAddress addressObj4 = null;
        InetSocketAddress addressObj6 = null;
        try
        {
            addressObj4 = NetworkUtils.getARecord(address, defaultPort);
        }
        catch (ParseException ex)
        {
            logger.error("Error parsing dns record.", ex);
        }
        try
        {
            addressObj6 = NetworkUtils.getAAAARecord(address, defaultPort);
        }
        catch (ParseException ex)
        {
            logger.error("Error parsing dns record.", ex);
        }

        if(preferIPv6Addresses)
        {
            if(addressObj6 != null && !resultAddresses.contains(addressObj6))
                resultAddresses.add(addressObj6);

            if(addressObj4 != null && !resultAddresses.contains(addressObj4))
                resultAddresses.add(addressObj4);
        }
        else
        {
            if(addressObj4 != null && !resultAddresses.contains(addressObj4))
                resultAddresses.add(addressObj4);

            if(addressObj6 != null && !resultAddresses.contains(addressObj6))
                resultAddresses.add(addressObj6);
        }

        if(addressObj4 == null && addressObj6 == null)
            logger.warn("No AAAA and A record found for " + address);
    }

    /**
     * Tries to resolve <tt>address</tt> into a valid InetSocketAddress using
     * an <tt>SRV</tt> query where it exists and A/AAAA where it doesn't. The
     * method assumes that the transport that we'll be using when connecting to
     * address is the one that has been defined as default for this provider.
     *
     * @param address the address we'd like to resolve.
     *
     * @return an <tt>InetSocketAddress</tt> instance containing the
     * <tt>SRV</tt> record for <tt>address</tt> if one has been defined and the
     * A/AAAA record where it hasn't.
     *
     * @throws UnknownHostException if <tt>address</tt> is not a valid host
     * address.
     */
    public InetSocketAddress resolveSipAddress(String address)
        throws UnknownHostException
    {
        ArrayList<InetSocketAddress> socketAddressesList =
            new ArrayList<InetSocketAddress>();

        resolveSipAddress(address, getDefaultTransport(), socketAddressesList,
                new ArrayList<String>(),
                getAccountID().getAccountPropertyBoolean(
                        ProtocolProviderFactory.PROXY_AUTO_CONFIG, false));

        return socketAddressesList.get(0);
    }

    /**
     * Resolves the SRV records add their corresponding AAAA and A records
     * in the <tt>resultAddresses</tt> ordered by the preference
     * <tt>preferIPv6Addresses</tt> and their corresponding <tt>transport</tt>
     * in the <tt>resultTransports</tt>.
     * @param address the address to resolve.
     * @param transport the transport to use
     * @param resultAddresses the result address after resolve.
     * @param resultTransports and the addresses transports.
     * @param preferIPv6Addresses is ipv6 addresses are preferred over ipv4.
     * @param srvQueryString is the supplied address is of type
     *  _sip(s)._protocol.address, a string ready for srv queries, used
     * when we have a NAPTR returned records with value <tt>true</tt>.
     * @throws ParseException exception when parsing dns address
     */
    private void resolveSRV(String address,
                            String transport,
                            List<InetSocketAddress> resultAddresses,
                            List<String> resultTransports,
                            boolean preferIPv6Addresses,
                            boolean srvQueryString)
            throws  ParseException
    {
        SRVRecord srvRecords[] = null;

        if(srvQueryString)
        {
            srvRecords = NetworkUtils.getSRVRecords(address);
        }
        else
        {
            srvRecords = NetworkUtils.getSRVRecords(
                    transport.equalsIgnoreCase(ListeningPoint.TLS)
                            ? "sips" : "sip",
                    transport.equalsIgnoreCase(ListeningPoint.UDP)
                            ? ListeningPoint.UDP : ListeningPoint.TCP,
                    address);
        }

        if(srvRecords != null)
        {
            ArrayList<InetSocketAddress> tempResultAddresses
                = new ArrayList<InetSocketAddress>();

            for(SRVRecord s : srvRecords)
            {
                // add corresponding A and AAAA records
                // to the host address from SRV records
                try
                {
                    // as these are already resolved addresses (the SRV res.)
                    // lets get it without triggering a PTR
                    resolveAddresses(
                        s.getTarget(),
                        tempResultAddresses,
                        preferIPv6Addresses,
                        s.getPort());
                }
                catch(UnknownHostException e)
                {
                    logger.warn("Address unknown:" + s.getTarget(), e);
                }

                /* add and every SRV address itself if not already there
                if(!tempResultAddresses.contains(s))
                    tempResultAddresses.add(s);
                */

                if (logger.isTraceEnabled())
                    logger.trace("Returned SRV " + s);
            }

            for(InetSocketAddress a : tempResultAddresses)
            {
                if(!resultAddresses.contains(a))
                {
                    resultAddresses.add(a);
                    resultTransports.add(transport);
                }
            }
        }
    }

    /**
     * Returns the <tt>InetAddress</tt> that is most likely to be to be used
     * as a next hop when contacting the specified <tt>destination</tt>. This is
     * an utility method that is used whenever we have to choose one of our
     * local addresses to put in the Via, Contact or (in the case of no
     * registrar accounts) From headers. The method also takes into account
     * the existence of an outbound proxy and in that case returns its address
     * as the next hop.
     *
     * @param destination the destination that we would contact.
     *
     * @return the <tt>InetSocketAddress</tt> that is most likely to be to be
     * used as a next hop when contacting the specified <tt>destination</tt>.
     *
     * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
     * host/ip/fqdn
     */
    public InetSocketAddress getIntendedDestination(Address destination)
        throws IllegalArgumentException
    {
        return getIntendedDestination((SipURI)destination.getURI());
    }


    /**
     * Returns the <tt>InetAddress</tt> that is most likely to be to be used
     * as a next hop when contacting the specified <tt>destination</tt>. This is
     * an utility method that is used whenever we have to choose one of our
     * local addresses to put in the Via, Contact or (in the case of no
     * registrar accounts) From headers. The method also takes into account
     * the existence of an outbound proxy and in that case returns its address
     * as the next hop.
     *
     * @param destination the destination that we would contact.
     *
     * @return the <tt>InetSocketAddress</tt> that is most likely to be to be
     * used as a next hop when contacting the specified <tt>destination</tt>.
     *
     * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
     * host/ip/fqdn
     */
    public InetSocketAddress getIntendedDestination(SipURI destination)
        throws IllegalArgumentException
    {
        return getIntendedDestination(destination.getHost());
    }

    /**
     * Returns the <tt>InetAddress</tt> that is most likely to be to be used
     * as a next hop when contacting the specified <tt>destination</tt>. This is
     * an utility method that is used whenever we have to choose one of our
     * local addresses to put in the Via, Contact or (in the case of no
     * registrar accounts) From headers. The method also takes into account
     * the existence of an outbound proxy and in that case returns its address
     * as the next hop.
     *
     * @param host the destination that we would contact.
     *
     * @return the <tt>InetSocketAddress</tt> that is most likely to be to be
     * used as a next hop when contacting the specified <tt>destination</tt>.
     *
     * @throws IllegalArgumentException if <tt>destination</tt> is not a valid
     * host/ip/fqdn.
     */
    public InetSocketAddress getIntendedDestination(String host)
        throws IllegalArgumentException
    {
        // Address
        InetSocketAddress destinationInetAddress = null;

        //resolveSipAddress() verifies whether our destination is valid
        //but the destination could only be known to our outbound proxy
        //if we have one. If this is the case replace the destination
        //address with that of the proxy.(report by Dan Bogos)
        InetSocketAddress outboundProxy = getOutboundProxy();

        if(outboundProxy != null)
        {
            if (logger.isTraceEnabled())
                logger.trace("Will use proxy address");
            destinationInetAddress = outboundProxy;
        }
        else
        {
            try
            {
                destinationInetAddress = resolveSipAddress(host);
            }
            catch (UnknownHostException ex)
            {
                throw new IllegalArgumentException(
                    host + " is not a valid internet address.",
                    ex);
            }
        }

        if(logger.isDebugEnabled())
            logger.debug("Returning address " + destinationInetAddress
                 + " for destination " + host);

        return destinationInetAddress;
    }

    /**
     * Stops dispatching SIP messages to a SIP protocol provider service
     * once it's been unregistered.
     *
     * @param event the change event in the registration state of a provider.
     */
    public void registrationStateChanged(RegistrationStateChangeEvent event)
    {
        if(event.getNewState() == RegistrationState.UNREGISTERED ||
           event.getNewState() == RegistrationState.CONNECTION_FAILED)
        {
            ProtocolProviderServiceSipImpl listener
                = (ProtocolProviderServiceSipImpl) event.getProvider();
            sipStackSharing.removeSipListener(listener);
            listener.removeRegistrationStateChangeListener(this);
        }
    }

    /**
     * Logs a specific message and associated <tt>Throwable</tt> cause as an
     * error using the current <tt>Logger</tt> and then throws a new
     * <tt>OperationFailedException</tt> with the message, a specific error code
     * and the cause.
     *
     * @param message the message to be logged and then wrapped in a new
     * <tt>OperationFailedException</tt>
     * @param errorCode the error code to be assigned to the new
     * <tt>OperationFailedException</tt>
     * @param cause the <tt>Throwable</tt> that has caused the necessity to log
     * an error and have a new <tt>OperationFailedException</tt> thrown
     * @param logger the logger that we'd like to log the error <tt>message</tt>
     * and <tt>cause</tt>.
     *
     * @throws OperationFailedException the exception that we wanted this method
     * to throw.
     */
    public static void throwOperationFailedException( String    message,
                                                      int       errorCode,
                                                      Throwable cause,
                                                      Logger    logger)
        throws OperationFailedException
    {
        logger.error(message, cause);

        if(cause == null)
            throw new OperationFailedException(message, errorCode);
        else
            throw new OperationFailedException(message, errorCode, cause);
    }

    /**
     * Check is property java.net.preferIPv6Addresses has default value
     * set in the application using our property:
     * net.java.sip.communicator.impl.protocol.sip.PREFER_IPV6_ADDRESSES
     * if not set - use setting from the system property.
     * @return is property java.net.preferIPv6Addresses <code>"true"</code>.
     */
    public static boolean checkPreferIPv6Addresses()
    {
        String defaultSetting = SipActivator.getResources().getSettingsString(
                PREFER_IPV6_ADDRESSES);
        if(!StringUtils.isNullOrEmpty(defaultSetting))
            return Boolean.parseBoolean(defaultSetting);

        // if there is no default setting return the system wide value.
        return Boolean.getBoolean("java.net.preferIPv6Addresses");
    }

    /**
     * Registers early message processor.
     * @param processor early message processor.
     */
    void addEarlyMessageProcessor(SipMessageProcessor processor)
    {
        synchronized (earlyProcessors)
        {
            if (!earlyProcessors.contains(processor))
            {
                this.earlyProcessors.add(processor);
            }
        }
    }

    /**
     * Removes the early message processor.
     * @param processor early message processor.
     */
    void removeEarlyMessageProcessor(SipMessageProcessor processor)
    {
        synchronized (earlyProcessors)
        {
            this.earlyProcessors.remove(processor);
        }
    }

    /**
     * Early process an incoming message from interested listeners.
     * @param message the message to process.
     */
    void earlyProcessMessage(EventObject message)
    {
        synchronized(earlyProcessors)
        {
            for (SipMessageProcessor listener : earlyProcessors)
            {
                try
                {
                    if(message instanceof RequestEvent)
                        listener.processMessage((RequestEvent)message);
                    else if(message instanceof ResponseEvent)
                        listener.processResponse((ResponseEvent)message, null);
                    else  if(message instanceof TimeoutEvent)
                        listener.processTimeout((TimeoutEvent)message, null);
                }
                catch(Throwable t)
                {
                    logger.error("Error pre-processing message", t);
                }
            }
        }
    }
}
TOP

Related Classes of net.java.sip.communicator.impl.protocol.sip.ProtocolProviderServiceSipImpl$ShutdownUnregistrationBlockListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.