Package org.cafesip.reference.jiplet

Source Code of org.cafesip.reference.jiplet.Subscription

/*
* Created on July 7, 2005
*
* Copyright 2005 CafeSip.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*  http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.cafesip.reference.jiplet;

import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.ResponseEvent;
import javax.sip.SipProvider;
import javax.sip.TimeoutEvent;
import javax.sip.address.Address;
import javax.sip.header.AcceptHeader;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.EventHeader;
import javax.sip.header.SubscriptionStateHeader;
import javax.sip.header.SupportedHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.cafesip.jiplet.Jiplet;
import org.cafesip.jiplet.JipletLogger;
import org.cafesip.jiplet.JipletTransaction;

/**
* @author Becky McElroy
*
*/
public class Subscription
{
    private String subscriptionId;

    private String eventId;

    private String eventType;

    private Address subscribingParty;

    private Address presentity;

    private String toTag;

    private Dialog dialog;

    private SipProvider provider;

    private String subscriptionState = SubscriptionStateHeader.ACTIVE;

    private String terminationReason = "";

    private long projectedExpiry = 0;

    private Jiplet parent;

    private SubscriptionList subscriptionList;

    private JipletTransaction transaction;

    public Subscription(Jiplet parent, SubscriptionList list,
            String subscriptionId, String eventId, String eventType,
            Address from, Address to, String toTag)
    {
        this.parent = parent;
        this.subscriptionList = list;
        this.subscriptionId = subscriptionId;
        this.eventId = eventId;
        this.eventType = eventType;
        this.subscribingParty = from;
        this.presentity = to;
        this.toTag = toTag;
    }

    public void dispose()
    {
        if (parent.isDebugEnabled() == true)
        {
            parent.debug("Subscription: Disposing Subscription with ID "
                    + subscriptionId + " from "
                    + subscribingParty.getURI().toString());
        }

        if (transaction != null)
        {
            parent.cancelResponse(transaction);
            transaction = null;
        }
    }
   
    public void sendNotify(SipProvider provider)
    {
        ContactInfo reg_info = (ContactInfo) LocationDatabase.getInstance()
                .get(dialog.getLocalParty().getURI().toString());

        sendNotify(provider, reg_info);
    }

    public void sendNotify()
    {
        sendNotify(this.provider);
    }

    public void sendNotify(ContactInfo reg_info)
    {
        sendNotify(this.provider, reg_info);
    }

    public void sendNotify(SipProvider provider, ContactInfo reg_info)
    // call setTimeLeft() or updateTimeLeft() before calling this method
    {
        if (provider == null)
        {
            parent.error("Null SipProvider, can't send NOTIFY");
            return;
        }

        this.provider = provider;
        if (parent.isDebugEnabled() == true)
        {
            parent.debug("Subscription: Creating NOTIFY message.");
        }

        try
        {
            Request req = dialog.createRequest(Request.NOTIFY);

            // later - handle challenges, add accumulated authorization hdrs
            // here

            EventHeader ehdr = parent.getHeaderFactory().createEventHeader(
                    eventType);
            if (eventId != null)
            {
                ehdr.setEventId(eventId);
            }
            req.addHeader(ehdr);

            String state = getSubscriptionState();
            SubscriptionStateHeader hdr = parent.getHeaderFactory()
                    .createSubscriptionStateHeader(state);

            if (state.equals(SubscriptionStateHeader.TERMINATED))
            {
                hdr.setReasonCode(getTerminationReason());
            }
            else if (state.equals(SubscriptionStateHeader.ACTIVE)
                    || state.equals(SubscriptionStateHeader.PENDING))
            {
                hdr.setExpires(getTimeLeft());
            }

            req.addHeader(hdr);

            AcceptHeader accept = parent.getHeaderFactory().createAcceptHeader(
                    "application", "pidf+xml");
            req.addHeader(accept);

            SupportedHeader supported = parent.getHeaderFactory()
                    .createSupportedHeader("ms-benotify"); // for messenger -
                                                            // needed?
            req.addHeader(supported);

            // now for the body

            String body = "";

            // if (subscriptionList.findPublishedInfo(to.toString())) // use
            // PUBLISH message info
            {

            }
            // else
            {
                // build the body that contains the presence information

                PresenceDocument doc = new PresenceDocument();
                doc.setPresentity(presentity.getURI().toString());

                PresenceTuple info = new PresenceTuple();
                info.setId("1"); // later, support more than one
                if ((reg_info == null)
                        || (state.equals(SubscriptionStateHeader.PENDING)))
                {
                    info.setStatus("closed");
                    info.setSubStatus("offline");
                }
                else
                {
                    info.setContactInfo(reg_info);
                    info.setStatus("open");
                    info.setSubStatus("online");
                }

                doc.addTuple(info);
                body = doc.encode();
            }

            ContentTypeHeader ct_hdr = parent.getHeaderFactory()
                    .createContentTypeHeader("application", "pidf+xml");

            req.setContent(body, ct_hdr);

            req.setContentLength(parent.getHeaderFactory()
                    .createContentLengthHeader(body.length()));

            /*
             *
             * The NOTIFY request MAY contain a body indicating the state of the
             * presentity. The means by which registration state is converted
             * into presence state is a matter of local policy, and beyond the
             * scope of this specification. However, some general guidelines can
             * be provided. The address-of-record in the registration (the To
             * header field) identifies the presentity. Each registered Contact
             * header field identifies a point of communications for that
             * presentity, which can be modeled using a tuple. Note that the
             * contact address in the tuple need not be the same as the
             * registered contact address. Using an address-of-record instead
             * allows subsequent communications from a watcher to pass through
             * proxies. This is useful for policy processing on behalf of the
             * presentity and the provider.
             *
             * A PUA that uses registrations to manipulate presence state SHOULD
             * make use of the SIP callee capabilities extension [9]. This
             * allows the PUA to provide the PA with richer information about
             * itself. For example, the presence of the methods parameter
             * listing the method "MESSAGE" indicates support for instant
             * messaging.
             *
             * later, when we suport multiple contact info's: The q values from
             * the Contact header field [1] can be used to establish relative
             * priorities amongst the various communications addresses in the
             * Contact header fields. see RFC 3859 (Section 4), RFC 2779
             * (5.1-5.3, 8.2)
             */

            /*
             * later: For reasons of privacy, it will frequently be necessary to
             * encrypt the contents of the notifications. This can be
             * accomplished using S/MIME. The encryption can be performed using
             * the key of the subscriber as identified in the From field of the
             * SUBSCRIBE request. Similarly, integrity of the notifications is
             * important to subscribers. As such, the contents of the
             * notifications MAY provide authentication and message integrity
             * using S/MIME. Since the NOTIFY is generated by the presence
             * server, which may not have access to the key of the user
             * represented by the presentity, it will frequently be the case
             * that the NOTIFY is signed by a third party. It is RECOMMENDED
             * that the signature be by an authority over the domain of the
             * presentity. In other words, for a user pres:user@example.com, the
             * signator of the NOTIFY SHOULD be the authority for example.com.
             */

            if (parent.isDebugEnabled() == true)
            {
                parent.debug("Subscription: Sending NOTIFY: " + req.toString());
            }

            ClientTransaction trans = provider.getNewClientTransaction(req);

            if (state.equals(SubscriptionStateHeader.TERMINATED) == false)
            {
                transaction = parent.registerForResponse(req, 5000);
                transaction.setAttribute("subscription", subscriptionId);
            }

            dialog.sendRequest(trans);

            if (parent.isDebugEnabled() == true)
            {
                parent.debug("Subscription: Sent NOTIFY to "
                        + subscribingParty.getURI().toString()
                        + " for Subscription ID " + subscriptionId);
            }

            if (state.equals(SubscriptionStateHeader.TERMINATED) == true)
            {
                transaction = null;
                subscriptionList.removeSubscription(this);
                dispose();
            }
        }
        catch (Exception e)
        {
            if (transaction != null)
            {
                parent.cancelResponse(transaction);
                transaction = null;
            }

            parent.error(e.getClass().getName() + ": " + e.getMessage() + "\n"
                    + JipletLogger.getStackTrace(e));
        }

    }

    public void processResponse(ResponseEvent responseEvent)
    {
        int status = responseEvent.getResponse().getStatusCode();

        if (parent.isDebugEnabled() == true)
        {
            parent.debug("Subscription: Processing " + status
                    + " response for Subscription with ID " + subscriptionId
                    + " from " + subscribingParty.getURI().toString());
        }

        if (status / 100 == 1)
        {
            return; // provisional response, keep waiting
        }

        transaction = null;

        if ((status == Response.UNAUTHORIZED)
                || (status == Response.PROXY_AUTHENTICATION_REQUIRED))
        {
            // later - modify the request to include user authorization info and
            // resend here
            // (new JipletTransaction)

            parent
                    .warn("Received authentication challenge at NOTIFY sending - not yet implemented");
            return;
        }

        if (status == Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST)
        {
            // If a NOTIFY request receives a 481 response, the notifier MUST
            // remove the corresponding subscription

            subscriptionList.removeSubscription(this);
            dispose();
            return;
        }

        // later: A NOTIFY request is considered failed if
        // a non-200 class response code is received which has no
        // "Retry-After" header and no implied further action which can be taken
        // to retry the request.

        if (getSubscriptionState().equals(SubscriptionStateHeader.TERMINATED) == true)
        {
            subscriptionList.removeSubscription(this);
            dispose();
        }
    }

    public void processTimeout(TimeoutEvent timeout)
    {
        // this method is called if there was no response to the NOTIFY request
        // we sent
        transaction = null;

        if (parent.isDebugEnabled() == true)
        {
            parent
                    .debug("Subscription: Processing NOTIFY response timeout for Subscription with ID "
                            + subscriptionId
                            + " from "
                            + subscribingParty.getURI().toString());
        }

        if (getSubscriptionState().equals(SubscriptionStateHeader.TERMINATED) == true)
        {
            subscriptionList.removeSubscription(this);
            dispose();
            return;
        }

        setTimeLeft(0);
        setSubscriptionState(SubscriptionStateHeader.TERMINATED);
        sendNotify();
    }

    public static String determineEventId(Request request) // assumes event
    // header
    // already validated
    {
        EventHeader ev = (EventHeader) request.getHeader(EventHeader.NAME);

        String event_id = ev.getEventId();
        if (event_id == null)
        {
            event_id = "";
        }

        return event_id;
    }

    /**
     * @return Returns the subscriptionState.
     */
    public String getSubscriptionState()
    {
        return subscriptionState;
    }

    /**
     * @param subscriptionState
     *            The subscriptionState to set.
     */
    public void setSubscriptionState(String state)
    {
        this.subscriptionState = state;
    }

    /**
     * @return Returns the timeLeft in seconds.
     */
    public int getTimeLeft()
    {
        if (projectedExpiry == 0)
        {
            return 0;
        }

        return (int) ((projectedExpiry - System.currentTimeMillis()) / 1000);
    }

    /**
     * @param timeLeft
     *            The timeLeft to set, in seconds.
     */
    public synchronized void setTimeLeft(int timeLeft)
    {
        if (timeLeft <= 0)
        {
            if ((terminationReason == null)
                    || (terminationReason.length() == 0))
            {
                setTerminationReason(SubscriptionStateHeader.TIMEOUT);
            }

            projectedExpiry = 0;
            return;
        }

        projectedExpiry = System.currentTimeMillis() + (timeLeft * 1000);
    }

    public void updateTimeLeft()
    {
        setTimeLeft(getTimeLeft());
    }

    /**
     * @return Returns the dialog.
     */
    public Dialog getDialog()
    {
        return dialog;
    }

    /**
     * @param dialog
     *            The dialog to set.
     */
    public void setDialog(Dialog dialog)
    {
        this.dialog = dialog;
    }

    /**
     * @return Returns the terminationReason.
     */
    public String getTerminationReason()
    {
        return terminationReason;
    }

    /**
     * @param terminationReason
     *            The terminationReason to set.
     */
    public void setTerminationReason(String terminationReason)
    {
        this.terminationReason = terminationReason;
    }

    public String getSubscriptionId()
    {
        return subscriptionId;
    }

    public void setSubscriptionId(String subscriptionId)
    {
        this.subscriptionId = subscriptionId;
    }

    public String getToTag()
    {
        return toTag;
    }

    public Address getSubscribingParty()
    {
        return subscribingParty;
    }

    public Address getPresentity()
    {
        return presentity;
    }

}
TOP

Related Classes of org.cafesip.reference.jiplet.Subscription

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.