FileDocCategorySizeDatePackage
SipRefreshHelper.javaAPI DocphoneME MR2 API (J2ME)14399Wed May 02 18:00:42 BST 2007javax.microedition.sip

SipRefreshHelper.java

/*
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */

package javax.microedition.sip;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.IllegalArgumentException;
import javax.microedition.sip.SipException;

import gov.nist.core.ParseException;
import gov.nist.microedition.sip.RefreshManager;
import gov.nist.microedition.sip.RefreshTask;
import gov.nist.microedition.sip.StackConnector;
import gov.nist.microedition.sip.SipClientConnectionImpl;
import gov.nist.siplite.address.Address;
import gov.nist.siplite.header.Header;
import gov.nist.siplite.header.CallIdHeader;
import gov.nist.siplite.header.ContactHeader;
import gov.nist.siplite.header.ContactList;
import gov.nist.siplite.header.ContentLengthHeader;
import gov.nist.siplite.header.ContentTypeHeader;
import gov.nist.siplite.header.ExpiresHeader;
import gov.nist.siplite.header.CSeqHeader;
import gov.nist.siplite.message.Request;

import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;

/**
 * This class implements the functionality that facilitates the handling of
 * refreshing requests on behalf of the application.
 * @see JSR180 spec, v 1.0.1, p 58-61
 *
 */
public class SipRefreshHelper {
    /**
     * The unique instance of this class
     */
    private static SipRefreshHelper instance = null;
    /** Refresh manager. */
    private static RefreshManager refreshManager = null;

    /**
     * Hide default constructor.
     */
    private SipRefreshHelper() {
    }
    
    /**
     * Returns the instance of SipRefreshHelper
     * @return the instance of SipRefreshHelper singleton
     */
    public static javax.microedition.sip.SipRefreshHelper getInstance() {
        if (instance == null) {
            instance = new SipRefreshHelper();
            refreshManager = RefreshManager.getInstance();
        }

        return instance;
    }

    /**
     * Stops refreshing a specific request related to refeshID. 
     * @see JSR180 spec, v 1.0.1, p 60
     *
     * @param refreshID the ID of the refresh to be stopped. If the ID
     * does not match any refresh task the method does nothing.
     */
    public void stop(int refreshID) {
        RefreshTask refreshTask = getRefreshTask(refreshID);
        if (refreshTask == null) {
            return;
        }

        Request requestNotCloned = refreshTask.getRequest();
        Request request = (Request)requestNotCloned.clone();
        requestNotCloned = null;
        removeBindings(refreshTask, request);

        doStop(refreshID, refreshTask);
    }

    /**
     * Updates one refreshed request with new values.
     * @see JSR180 spec, v 1.0.1, p 60
     *
     * @param type value of Content-Type (null or empty, no content)
     * @param length value of Content-Length (<=0, no content)
     * @param expires value of Expires (-1, no Expires header),
     * (0, stop the refresh)
     * @return Returns the OutputStream to fill the content. If the update does
     * not have new content (type = null and/or length = 0) method returns null
     * and the message is sent automatically.
     * @throws java.lang.IllegalArgumentException if some input parameter
     * is invalid
     */
    public java.io.OutputStream update(
            int refreshID,
            java.lang.String[] contact,
            java.lang.String type,
            int length,
            int expires) {
        String taskId;
        try {
            taskId = String.valueOf(refreshID);
        } catch (NumberFormatException nfe) {
            return null;
        }

        RefreshTask refreshTask = refreshManager.getTask(taskId);
        if (refreshTask == null) {
            return null;
        }

        // System.out.println(">>> update, type = " + type);

        Request requestNotCloned = refreshTask.getRequest();
        Request request = (Request)requestNotCloned.clone();
        requestNotCloned = null;

        // Contacts
        if (contact != null && contact.length != 0) {
            // JSR180: contact replaces all old values.
            // Multiple Contact header values are applicable
            // only for REGISTER method.
            if (contact.length > 1 &&
                    !request.getMethod().equals(Request.REGISTER)) {
                throw new IllegalArgumentException(
                    "Only one contact is allowed for non-REGISTER requests.");
            }

            // Remove old contacts from the request.
            ContactList cl = request.getContactHeaders();
            int n = (cl != null) ? cl.size() : 0;

            for (int i = 0; i < n; i++) {
                request.removeHeader(ContactHeader.NAME);
            }

            // Add new contacts to the request.
            for (int i = 0; i < contact.length; i++) {
                String contactURI = contact[i];
                Address address = null;

                try {
                    address = StackConnector.addressFactory
                            .createAddress(contactURI);
                } catch (ParseException pe) {
                    throw new
                            IllegalArgumentException("one of the contact "
                            + "addresses is not valid");
                }

                try {
                    ContactHeader contactHeader = StackConnector.headerFactory.
                        createContactHeader(address);
                    request.addHeader(contactHeader);
                } catch (SipException ex) {
                    throw new IllegalArgumentException(ex.getMessage());
                }
            }
        }

        // Expires
        if (expires == -1) {
            request.removeHeader(ExpiresHeader.NAME);
        } else if (expires >= 0) {
            if (expires == 0) {
                // Stop the refresh
                removeBindings(refreshTask, request);
                doStop(refreshID, refreshTask);

                // Ambiguousness in the JSR180 spec:
                // "expires = 0 has the same effect as calling stop(refreshID)"
                // But: "update() returns the OutputStream to fill the content.
                // If the update does not have new content (type = null and/or
                // length = 0) method returns null and the message is sent
                // automatically".
                // It is not clear what to do if the expires is 0 and the
                // content is not null. If the refresh is stopped, we can't
                // provide the OutputStream to fill the content, so return null.
                return null;
            }

            ExpiresHeader eh = (ExpiresHeader)request.getHeader(
                                                  ExpiresHeader.NAME);
            if (eh == null) {
                // Try to add an ExpiresHeader
                try {
                    eh = StackConnector.headerFactory.
                            createExpiresHeader(expires);
                    request.addHeader(eh);
                } catch (SipException ex) {
                    throw new IllegalArgumentException(ex.getMessage());
                }
            }

            eh.setExpires(expires);
        } else {
            throw new
                IllegalArgumentException("the expires value is not correct");
        }

        // Content Length
        ContentLengthHeader contentLengthHeader =
                request.getContentLengthHeader();

        if (contentLengthHeader == null) {
            try {
                request.addHeader(StackConnector.headerFactory.
                    createContentLengthHeader(0));
            } catch (SipException ex) {
                throw new IllegalArgumentException(ex.getMessage());
            }
        }

        // Content Type
        if (length > 0 && type != null && !type.equals("")) {
            request.removeHeader(Header.CONTENT_TYPE);

            Exception ex = null;
            try {
                ContentTypeHeader contentTypeHeader = (ContentTypeHeader)
                    StackConnector.headerFactory.createHeader(
                        Header.CONTENT_TYPE, type);
                request.addHeader(contentTypeHeader);
            } catch (ParseException pe) {
                ex = pe;
            } catch (SipException se) {
                ex = se;
            }
            if (ex != null) {
                throw new IllegalArgumentException(ex.getMessage());
            }

            request.getContentLengthHeader().setContentLength(length);
        }

        // Call-Id header was added in SipClientConnectionImpl.send()
        // when the initial request was sent. This is the reason why
        // Call-Id header is not added here.

        // Cancel the timer
        refreshTask.cancel();

        SipClientConnectionImpl sipClientConnection =
                (SipClientConnectionImpl)refreshTask.getSipClientConnection();

        if (type == null || type.equals("") || length <= 0) {
            // If the is no new content, send the message automatically
            // Don't call sipClientConnection.send() directly
            // because CSeq header must be updated.
            try {
                refreshTask.updateAndSendRequest(request);
            } catch (IOException ex) {
                if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                    Logging.report(Logging.WARNING, LogChannels.LC_JSR180,
                        "SipRefreshHelper.update(): " + ex);
                }
            }

            return null;
        } else {
            OutputStream contentOutputStream = null;

            try {
                // set the content
                // contentOutputStream = sipClientConnection
                //         .openContentOutputStream();
                contentOutputStream =
                    refreshTask.updateRequestAndOpenOutputStream(request);
            } catch (IOException ioe) {
                return null;
            }

            return contentOutputStream;
        }
    }

    /**
     * Finds a refresh task corresponding to the given refreshID.
     * @param refreshID the ID of the refresh.
     * @return RefreshTask object that corresponds to the given refreshID
     * or null if there is no such task.
     */
    private RefreshTask getRefreshTask(int refreshID) {
        String taskId;

        try {
            taskId = String.valueOf(refreshID);
        } catch (NumberFormatException nfe) {
            return null;
        }

        return refreshManager.getTask(taskId);
    }

    /**
     * Stops refreshing a specific request related to refeshID without
     * removing the possible binding between end point and registrar/notifier.
     * @param refreshID the ID of the refresh to be stopped. If the ID
     * does not match any refresh task the method does nothing.
     * @param refreshTask refresh task that corresponds to the given refreshID.
     */
    private void doStop(int refreshID, RefreshTask refreshTask) {
        String taskId;

        try {
            taskId = String.valueOf(refreshID);
        } catch (NumberFormatException nfe) {
            return;
        }

        // Cancel the timer
        refreshTask.cancel();
        refreshTask.getSipRefreshListener().refreshEvent(
                refreshID,
                0,
                "refresh stopped");
        refreshManager.removeTask(taskId);
    }

    /**
     * Removes the possible binding between end point and registrar/notifier
     * as described in RFC 3261, section 10.2.2, Removing Bindings:
     * Registrations are soft state and expire unless refreshed, but can also
     * be explicitly removed. A client can attempt to influence the expiration
     * interval selected by the registrar as described in Section 10.2.1. A UA
     * requests the immediate removal of a binding by specifying an expiration
     * interval of "0" for that contact address in a REGISTER request.
     * @param refreshTask the refresh task that refreshes the given request.
     * @param request the request that will be sent with the header Expires = 0.
     */
    private void removeBindings(RefreshTask refreshTask, Request request) {
        ExpiresHeader eh = (ExpiresHeader)request.getHeader(ExpiresHeader.NAME);

        if (eh == null) {
            Exception ex = null;
            // Try to add an ExpiresHeader
            try {
                eh = StackConnector.headerFactory.createExpiresHeader(0);
                request.addHeader(eh);
            } catch (SipException se) {
                ex = se;
            } catch (IllegalArgumentException iae) {
                ex = iae;
            }
            if (ex != null) {
                if (Logging.REPORT_LEVEL <= Logging.ERROR) {
                    Logging.report(Logging.ERROR, LogChannels.LC_JSR180,
                        "SipRefreshHelper.removeBindings(): " + ex);
                }
                return;
            }
        }

        eh.setExpires(0);

        // Update the request of the sipClientConnection
        // and send it immediately.

        try {
            refreshTask.updateAndSendRequest(request);
        } catch (IOException ex) {
            if (Logging.REPORT_LEVEL <= Logging.WARNING) {
                Logging.report(Logging.WARNING, LogChannels.LC_JSR180,
                    "SipRefreshHelper.removeBindings(): " + ex);
            }
        }
    }
}