[sip-comm-cvs] CVS update: /sip-communicator/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java

emcho at dev.java.net emcho at dev.java.net
Wed May 16 16:51:23 CEST 2007


User: emcho   
Date: 2007-05-16 14:51:23+0000
Log:
 Adding Ben's implementation of instant messaging for SIP (by Benoit Pradelle)
 
 Added support instant messaging in SIP 

File Changes:

Directory: /sip-communicator/src/net/java/sip/communicator/impl/protocol/sip/
=============================================================================

File [added]: OperationSetBasicInstantMessagingSipImpl.java
Url: https://sip-communicator.dev.java.net/source/browse/sip-communicator/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java?rev=1.1&content-type=text/vnd.viewcvs-markup
Added lines: 867
----------------
/*
 * 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 java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.*;
import java.io.*;

import javax.sip.*;
import javax.sip.address.*;
import javax.sip.header.*;
import javax.sip.message.*;

import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.Message;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;

/**
 * A straightforward implementation of the basic instant messaging operation
 * set.
 *
 * @author Benoit Pradelle
 */
public class OperationSetBasicInstantMessagingSipImpl
    implements OperationSetBasicInstantMessaging
{
    private static final Logger logger =
        Logger.getLogger(OperationSetBasicInstantMessagingSipImpl.class);

    /**
     * A list of listeneres registered for message events.
     */
    private Vector messageListeners = new Vector();

    /**
     * The provider that created us.
     */
    private ProtocolProviderServiceSipImpl sipProvider = null;

    /**
     * A reference to the persistent presence operation set that we use
     * to match incoming messages to <tt>Contact</tt>s and vice versa.
     */
    private OperationSetPersistentPresenceSipImpl opSetPersPresence = null;

    /**
     * Hashtable containing the CSeq of each discussion
     */
    private Hashtable cseqs = null;

    /**
     * Creates an instance of this operation set.
     * @param provider a ref to the <tt>ProtocolProviderServiceImpl</tt>
     * that created us and that we'll use for retrieving the underlying aim
     * connection.
     */
    OperationSetBasicInstantMessagingSipImpl(
        ProtocolProviderServiceSipImpl provider)
    {
        this.sipProvider = provider;
        this.cseqs = new Hashtable();
        provider.addRegistrationStateChangeListener(new
            RegistrationStateListener());
    }

    /**
     * Registeres a MessageListener with this operation set so that it gets
     * notifications of successful message delivery, failure or reception of
     * incoming messages..
     *
     * @param listener the <tt>MessageListener</tt> to register.
     */
    public void addMessageListener(MessageListener listener)
    {
        synchronized (this.messageListeners)
        {
            if (!this.messageListeners.contains(listener))
            {
                this.messageListeners.add(listener);
            }
        }
    }

    /**
     * Unregisteres <tt>listener</tt> so that it won't receive any further
     * notifications upon successful message delivery, failure or reception of
     * incoming messages..
     *
     * @param listener the <tt>MessageListener</tt> to unregister.
     */
    public void removeMessageListener(MessageListener listener)
    {
        synchronized (this.messageListeners)
        {
            this.messageListeners.remove(listener);
        }
    }

    /**
     * Create a Message instance for sending arbitrary MIME-encoding content.
     *
     * @param content content value
     * @param contentType the MIME-type for <tt>content</tt>
     * @param contentEncoding encoding used for <tt>content</tt>
     * @param subject a <tt>String</tt> subject or <tt>null</tt> for now subject.
     * @return the newly created message.
     */
    public Message createMessage(byte[] content, String contentType,
                                 String contentEncoding, String subject)
    {
        return new MessageSipImpl(new String(content), contentType
                                  , contentEncoding, subject);
    }

    /**
     * Create a Message instance for sending a simple text messages with
     * default (text/plain) content type and encoding.
     *
     * @param messageText the string content of the message.
     * @return Message the newly created message
     */
    public Message createMessage(String messageText)
    {
        return new MessageSipImpl(messageText, DEFAULT_MIME_TYPE
                                  , DEFAULT_MIME_ENCODING, null);
    }

    /**
     * Determines wheter the protocol provider (or the protocol itself) support
     * sending and receiving offline messages. Most often this method would
     * return true for protocols that support offline messages and false for
     * those that don't. It is however possible for a protocol to support these
     * messages and yet have a particular account that does not (i.e. feature
     * not enabled on the protocol server). In cases like this it is possible
     * for this method to return true even when offline messaging is not
     * supported, and then have the sendMessage method throw an
     * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED.
     *
     * @return <tt>true</tt> if the protocol supports offline messages and
     * <tt>false</tt> otherwise.
     */
    public boolean isOfflineMessagingSupported()
    {
        return false;
    }

    /**
     * Sends the <tt>message</tt> to the destination indicated by the
     * <tt>to</tt> contact.
     *
     * @param to the <tt>Contact</tt> to send <tt>message</tt> to
     * @param message the <tt>Message</tt> to send.
     * @throws java.lang.IllegalStateException if the underlying stack is
     * not registered and initialized.
     * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an
     * instance of ContactImpl.
     */
    public void sendInstantMessage(Contact to, Message message)
        throws IllegalStateException, IllegalArgumentException
    {
        if (! (to instanceof ContactSipImpl))
            throw new IllegalArgumentException(
                "The specified contact is not a Sip contact."
                + to);

        assertConnected();

        // no offline message
        if (to.getPresenceStatus().equals(SipStatusEnum.OFFLINE))
        {
            MessageDeliveryFailedEvent evt =
                new MessageDeliveryFailedEvent(
                    message,
                    to,
                    MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED,
                    new Date());
            fireMessageEvent(evt);
            return;
        }

        // create the message
        Request mes;
        try
        {
            mes = createMessage(to, message);
        }
        catch (OperationFailedException ex)
        {
            logger.error(
                "Failed to create the message."
                , ex);

            MessageDeliveryFailedEvent evt =
                new MessageDeliveryFailedEvent(
                    message,
                    to,
                    MessageDeliveryFailedEvent.INTERNAL_ERROR,
                    new Date());
            fireMessageEvent(evt);
            return;
        }

        //Transaction
        ClientTransaction messageTransaction;
        SipProvider jainSipProvider
            = this.sipProvider.getDefaultJainSipProvider();
        try
        {
            messageTransaction = jainSipProvider.getNewClientTransaction(mes);
        }
        catch (TransactionUnavailableException ex)
        {
            logger.error(
                "Failed to create messageTransaction.\n"
                + "This is most probably a network connection error."
                , ex);

            MessageDeliveryFailedEvent evt =
                new MessageDeliveryFailedEvent(
                    message,
                    to,
                    MessageDeliveryFailedEvent.NETWORK_FAILURE,
                    new Date());
            fireMessageEvent(evt);
            return;
        }

        // send the message
        try
        {
            messageTransaction.sendRequest();
        }
        catch (SipException ex)
        {
            logger.error(
                "Failed to send the message."
                , ex);

            MessageDeliveryFailedEvent evt =
                new MessageDeliveryFailedEvent(
                    message,
                    to,
                    MessageDeliveryFailedEvent.INTERNAL_ERROR,
                    new Date());
            fireMessageEvent(evt);
            return;
        }
    }

    /**
     * Construct a Request which represent a new message
     *
     * @param to the <tt>Contact</tt> to send <tt>message</tt> to
     * @param message the <tt>Message</tt> to send.
     * @return a Message Request destinated to the contact
     * @throws OperationFailedException if an error occured during
     * the creation of the request
     */
    private Request createMessage(Contact to, Message message)
        throws OperationFailedException
    {
        // Address
        InetAddress destinationInetAddress = null;
        Address toAddress = null;
        try
        {
            toAddress = parseAddressStr(to.getAddress());

            destinationInetAddress = InetAddress.getByName(
                ( (SipURI) toAddress.getURI()).getHost());
        }
        catch (UnknownHostException ex)
        {
            throw new IllegalArgumentException(
                ( (SipURI) toAddress.getURI()).getHost()
                + " is not a valid internet address " + ex.getMessage());
        }
        catch (ParseException exc)
        {
            //Shouldn't happen
            logger.error(
                "An unexpected error occurred while"
                + "constructing the address", exc);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the address"
                , OperationFailedException.INTERNAL_ERROR
                , exc);
        }

        // Call ID
        CallIdHeader callIdHeader = this.sipProvider
            .getDefaultJainSipProvider().getNewCallId();

        //CSeq
        CSeqHeader cSeqHeader = null;

        // find the next CSeq value for this contact
        long seqN = 1;
        if (this.cseqs.containsKey(to))
        {
            seqN = ( (Long)this.cseqs.get(to)).longValue();
            this.cseqs.put(to, new Long(seqN + 1));
        }
        else
        {
            this.cseqs.put(to, new Long(seqN + 1));
        }

        try
        {
            cSeqHeader = this.sipProvider.getHeaderFactory()
                .createCSeqHeader(seqN, Request.MESSAGE);
        }
        catch (InvalidArgumentException ex)
        {
            //Shouldn't happen
            logger.error(
                "An unexpected error occurred while"
                + "constructing the CSeqHeadder", ex);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the CSeqHeadder"
                , OperationFailedException.INTERNAL_ERROR
                , ex);
        }
        catch (ParseException exc)
        {
            //shouldn't happen
            logger.error(
                "An unexpected error occurred while"
                + "constructing the CSeqHeadder", exc);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the CSeqHeadder"
                , OperationFailedException.INTERNAL_ERROR
                , exc);
        }

        //FromHeader and ToHeader
        String localTag = ProtocolProviderServiceSipImpl.generateLocalTag();
        FromHeader fromHeader = null;
        ToHeader toHeader = null;
        try
        {
            //FromHeader
            fromHeader = this.sipProvider.getHeaderFactory()
                .createFromHeader(this.sipProvider.getOurSipAddress()
                                  , localTag);

            //ToHeader
            toHeader = this.sipProvider.getHeaderFactory()
                .createToHeader(toAddress, null);
        }
        catch (ParseException ex)
        {
            //these two should never happen.
            logger.error(
                "An unexpected error occurred while"
                + "constructing the FromHeader or ToHeader", ex);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the FromHeader or ToHeader"
                , OperationFailedException.INTERNAL_ERROR
                , ex);
        }

        //ViaHeaders
        ArrayList viaHeaders = this.sipProvider.getLocalViaHeaders(
            destinationInetAddress
            , this.sipProvider.getDefaultListeningPoint());

        //MaxForwards
        MaxForwardsHeader maxForwards = this.sipProvider
            .getMaxForwardsHeader();

        // Content params
        ContentTypeHeader contTypeHeader;
        ContentEncodingHeader contEncodHeader;
        ContentLengthHeader contLengthHeader;
        try
        {
            contTypeHeader = this.sipProvider.getHeaderFactory()
                .createContentTypeHeader(getType(message),
                                         getSubType(message));

            contEncodHeader = this.sipProvider.getHeaderFactory()
                .createContentEncodingHeader(message.getEncoding());

            contLengthHeader = this.sipProvider.getHeaderFactory()
                .createContentLengthHeader(message.getSize());
        }
        catch (ParseException ex)
        {
            //these two should never happen.
            logger.error(
                "An unexpected error occurred while"
                + "constructing the content headers", ex);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the content headers"
                , OperationFailedException.INTERNAL_ERROR
                , ex);
        }
        catch (InvalidArgumentException exc)
        {
            //these two should never happen.
            logger.error(
                "An unexpected error occurred while"
                + "constructing the content length header", exc);
            throw new OperationFailedException(
                "An unexpected error occurred while"
                + "constructing the content length header"
                , OperationFailedException.INTERNAL_ERROR
                , exc);
        }

        Request req;
        try
        {
            req = this.sipProvider.getMessageFactory().createRequest(
                toHeader.getAddress().getURI(),
                Request.MESSAGE,
                callIdHeader,
                cSeqHeader,
                fromHeader,
                toHeader,
                viaHeaders,
                maxForwards,
                contTypeHeader,
                message.getContent().getBytes());
        }
        catch (ParseException ex)
        {
            //shouldn't happen
            logger.error(
                "Failed to create message Request!", ex);
            throw new OperationFailedException(
                "Failed to create message Request!"
                , OperationFailedException.INTERNAL_ERROR
                , ex);
        }

        req.addHeader(contEncodHeader);
        req.addHeader(contLengthHeader);

        return req;
    }

    /**
     * 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.
     */
    private Address parseAddressStr(String uriStr)
        throws ParseException
    {
        uriStr = uriStr.trim();

        //Handle default domain name (i.e. transform 1234 -> 1234 at sip.com)
        //assuming that if no domain name is specified then it should be the
        //same as ours.
        if (uriStr.indexOf('@') == -1
            && !uriStr.trim().startsWith("tel:"))
        {
            uriStr = uriStr + "@"
                + ( (SipURI)this.sipProvider.getOurSipAddress().getURI())
                .getHost();
        }

        //Let's be uri fault tolerant and add the sip: scheme if there is none.
        if (uriStr.toLowerCase().indexOf("sip:") == -1 //no sip scheme
            && uriStr.indexOf('@') != -1) //most probably a sip uri
        {
            uriStr = "sip:" + uriStr;
        }

        //Request URI
        Address uri
            = this.sipProvider.getAddressFactory().createAddress(uriStr);

        return uri;
    }

    /**
     * Parses the content type of a message and return the type
     *
     * @param m the Message to scan
     * @return the type of the message
     */
    private String getType(Message m)
    {
        String type = m.getContentType();

        return type.substring(0, type.indexOf('/'));
    }

    /**
     * Parses the content type of a message and return the subtype
     *
     * @param m the Message to scan
     * @return the subtype of the message
     */
    private String getSubType(Message m)
    {
        String subtype = m.getContentType();

        return subtype.substring(subtype.indexOf('/') + 1);
    }

    /**
     * Utility method throwing an exception if the stack is not properly
     * initialized.
     * @throws java.lang.IllegalStateException if the underlying stack is
     * not registered and initialized.
     */
    private void assertConnected()
        throws IllegalStateException
    {
        if (this.sipProvider == null)
            throw new IllegalStateException(
                "The provider must be non-null and signed on the "
                + "service before being able to communicate.");
        if (!this.sipProvider.isRegistered())
            throw new IllegalStateException(
                "The provider must be signed on the service before "
                + "being able to communicate.");
    }

    /**
     * Our listener that will tell us when we're registered to
     */
    private class RegistrationStateListener
        implements RegistrationStateChangeListener
    {
        /**
         * The method is called by a ProtocolProvider implementation whenver
         * a change in the registration state of the corresponding provider had
         * occurred.
         * @param evt ProviderStatusChangeEvent the event describing the status
         * change.
         */
        public void registrationStateChanged(RegistrationStateChangeEvent evt)
        {
            logger.debug("The provider changed state from: "
                         + evt.getOldState()
                         + " to: " + evt.getNewState());

            if (evt.getNewState() == RegistrationState.REGISTERED)
            {
                opSetPersPresence = (OperationSetPersistentPresenceSipImpl)
                    sipProvider.getSupportedOperationSets()
                    .get(OperationSetPersistentPresence.class.getName());

                sipProvider.registerMethodProcessor(Request.MESSAGE,
                    new SipMessageListener());
            }
        }
    }

    /**
     * Delivers the specified event to all registered message listeners.
     * @param evt the <tt>EventObject</tt> that we'd like delivered to all
     * registered message listerners.
     */
    private void fireMessageEvent(EventObject evt)
    {
        Iterator listeners = null;
        synchronized (this.messageListeners)
        {
            listeners = new ArrayList(this.messageListeners).iterator();
        }

        while (listeners.hasNext())
        {
            MessageListener listener
                = (MessageListener) listeners.next();

            if (evt instanceof MessageDeliveredEvent)
            {
                listener.messageDelivered( (MessageDeliveredEvent) evt);
            }
            else if (evt instanceof MessageReceivedEvent)
            {
                listener.messageReceived( (MessageReceivedEvent) evt);
            }
            else if (evt instanceof MessageDeliveryFailedEvent)
            {
                listener.messageDeliveryFailed(
                    (MessageDeliveryFailedEvent) evt);
            }
        }
    }

    /**
     * Class for listening incoming packets.
     */
    private class SipMessageListener
        implements SipListener
    {
        public void processDialogTerminated(
            DialogTerminatedEvent dialogTerminatedEvent)
        {
            // never fired
        }

        public void processIOException(IOExceptionEvent exceptionEvent)
        {
            // never fired
        }

        public void processTransactionTerminated(
            TransactionTerminatedEvent transactionTerminatedEvent)
        {
            // nothing to do
        }

        public void processTimeout(TimeoutEvent timeoutEvent)
        {
            /** @todo implement a retransmit ?? */
        }

        /**
         * Process a request from a distant contact
         *
         * @param requestEvent the <tt>RequestEvent</tt> containing the newly
         * received request.
         */
        public void processRequest(RequestEvent requestEvent)
        {
            // get the content
            String content = null;

            try
            {
                content = new String(
                    requestEvent.getRequest().getRawContent(),
                    requestEvent.getRequest().getContentEncoding()
                    .getEncoding());
            }
            catch (UnsupportedEncodingException ex)
            {
                logger.debug("failed to convert the message charset");
                content = new String(requestEvent.getRequest().getRawContent());
            }

            // who sent this request ?
            FromHeader fromHeader = (FromHeader)
                requestEvent.getRequest().getHeader(FromHeader.NAME);

            if (fromHeader == null)
            {
                logger.error("received a request without a from header");
                return;
            }

            Contact from = opSetPersPresence.findContactByID(
                fromHeader.getAddress().getURI().toString());
            Message newMessage = createMessage(content);

            // first message
            if (from == null)
            {
                logger.debug("received a message from an unknown contact: "
                             + from);
                //create the volatile contact
                from = opSetPersPresence
                    .createVolatileContact(fromHeader.getAddress().getURI()
                                           .toString());
            }

            // answer ok
            try
            {
                Response ack = sipProvider.getMessageFactory()
                    .createResponse(Response.OK, requestEvent.getRequest());
                SipProvider jainSipProvider = (SipProvider) requestEvent.
                    getSource();
                jainSipProvider.getNewServerTransaction(
                    requestEvent.getRequest()).sendResponse(ack);
            }
            catch (ParseException ex)
            {
                logger.error("failed to build the response");
            }
            catch (SipException exc)
            {
                logger.error("failed to send the response : "
                             + exc.getMessage());
            }
            catch (InvalidArgumentException exc1)
            {
                logger.debug("Invalid argument for createResponse : "
                             + exc1.getMessage());
            }

            // fire an event
            MessageReceivedEvent msgReceivedEvt
                = new MessageReceivedEvent(
                    newMessage, from, new Date());
            fireMessageEvent(msgReceivedEvt);
        }

        /**
         * Process a response from a distant contact.
         *
         * @param responseEvent the <tt>ResponseEvent</tt> containing the newly
         * received SIP response.
         */
        public void processResponse(ResponseEvent responseEvent)
        {
            Request req = responseEvent.getClientTransaction().getRequest();
            int status = responseEvent.getResponse().getStatusCode();
            // content of the response
            String content = null;

            try
            {
                content = new String(
                    req.getRawContent(),
                    req.getContentEncoding()
                    .getEncoding());
            }
            catch (UnsupportedEncodingException ex)
            {
                logger.debug("failed to convert the message charset");
                content = new String(req.getRawContent());
            }

            // who sent the original message ?
            FromHeader fromHeader = (FromHeader)
                req.getHeader(FromHeader.NAME);

            if (fromHeader == null)
            {
                logger.error("received a response without a from header");
                return;
            }

            Contact from = opSetPersPresence.findContactByID(
                fromHeader.getAddress().getURI().toString());
            Message newMessage = createMessage(content);

            if (from == null)
            {
                logger.error(
                    "Error received a response from an unknown contact : "
                    + responseEvent.getResponse().getReasonPhrase());
                return;
            }

            // status 401/407 = proxy authentification
            if (status >= 400 && status != 401 && status != 407)
            {
                logger.error(
                    "Error received from the network : "
                    + responseEvent.getResponse().getReasonPhrase());

                // error for delivering the message
                MessageDeliveryFailedEvent evt =
                    new MessageDeliveryFailedEvent(
                        newMessage,
                        from,
                        MessageDeliveryFailedEvent.INTERNAL_ERROR,
                        new Date());
                fireMessageEvent(evt);
            }
            else if (status == 401 || status == 407)
            {
                // proxy ask for authentification
                logger.debug(
                    "proxy asks authentication : "
                    + responseEvent.getResponse().getReasonPhrase());

                ClientTransaction clientTransaction = responseEvent
                    .getClientTransaction();
                SipProvider sourceProvider = (SipProvider)
                    responseEvent.getSource();

                try
                {
                    processAuthenticationChallenge(clientTransaction,
                        responseEvent.getResponse(),
                        sourceProvider);
                }
                catch (OperationFailedException ex)
                {
                    // error for delivering the message
                    MessageDeliveryFailedEvent evt =
                        new MessageDeliveryFailedEvent(
                            newMessage,
                            from,
                            MessageDeliveryFailedEvent.INTERNAL_ERROR,
                            new Date());
                    fireMessageEvent(evt);
                }

            }
            else if (status >= 200)
            {
                logger.debug(
                    "Ack received from the network : "
                    + responseEvent.getResponse().getReasonPhrase());

                // we delivered the message
                MessageDeliveredEvent msgDeliveredEvt
                    = new MessageDeliveredEvent(
                        newMessage, from, new Date());

                fireMessageEvent(msgDeliveredEvt);
            }
        }

        /**
         * Attempts to re-generate the corresponding request with the proper
         * credentials.
         *
         * @param clientTransaction the corresponding transaction
         * @param response the challenge
         * @param jainSipProvider the provider that received the challenge
         *
         * @throws OperationFailedException if processing the authentication
         * challenge fails.
         */
        private void processAuthenticationChallenge(
            ClientTransaction clientTransaction,
            Response response,
            SipProvider jainSipProvider)
            throws OperationFailedException
        {
            try
            {
                logger.debug("Authenticating a message request.");

                ClientTransaction retryTran
                    = sipProvider.getSipSecurityManager().handleChallenge(
                        response
                        , clientTransaction
                        , jainSipProvider);

                retryTran.sendRequest();
                return;
            }
            catch (Exception exc)
            {
                logger.error("We failed to authenticate a message request.",
                             exc);

                throw new OperationFailedException("Failed to authenticate"
                    + "a message request"
                    , OperationFailedException.INTERNAL_ERROR
                    , exc);
            }
        }
    }
}





---------------------------------------------------------------------
To unsubscribe, e-mail: cvs-unsubscribe at sip-communicator.dev.java.net
For additional commands, e-mail: cvs-help at sip-communicator.dev.java.net





More information about the commits mailing list