diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java
index 03e56bf2f..b04bfda09 100644
--- a/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/sip/ContactSipImpl.java
@@ -9,6 +9,8 @@
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
+import javax.sip.*;
+
/**
* A simple, straightforward implementation of a SIP Contact. Since
* the SIP protocol is not a real one, we simply store all contact details
@@ -18,6 +20,7 @@
* consulting the encapsulated object.
*
* @author Emil Ivov
+ * @author Benoit Pradelle
*/
public class ContactSipImpl
implements Contact
@@ -55,8 +58,24 @@ public class ContactSipImpl
* Determines whether the contact has been resolved (i.e. we have a
* confirmation that it is still on the server contact list).
*/
- private boolean isResolved = true;
-
+ private boolean isResolved = false;
+
+ /**
+ * Determines whether this contact can be resolved or if he will be
+ * never resolved (for example if he doesn't support SIMPLE)
+ */
+ private boolean isResolvable = true;
+
+ /**
+ * Stores the dialog used for receiving the contact status
+ */
+ private Dialog clientDialog = null;
+
+ /**
+ * Stores the dialog used for communicate our status to this contact
+ */
+ private Dialog serverDialog = null;
+
/**
* Creates an instance of a meta contact with the specified string used
* as a name and identifier.
@@ -231,6 +250,50 @@ public String getPersistentData()
{
return null;
}
+
+ /**
+ * Sets the client dialog associated with this contact.
+ * The client dialog is the dialog we use to retrieve the presence
+ * state of this contact.
+ *
+ * @param clientDialog the new clientDialog to use
+ */
+ public void setClientDialog(Dialog clientDialog) {
+ this.clientDialog = clientDialog;
+ }
+
+ /**
+ * Returns the client dialog associated with this contact.
+ * The client dialog is the dialog we use to retrieve the presence
+ * state of this contact.
+ *
+ * @return the clientDialog associated with the contact
+ */
+ public Dialog getClientDialog() {
+ return this.clientDialog;
+ }
+
+ /**
+ * Sets the server dialog associated with this contact.
+ * The server dialog is the dialog we use to send our presence status
+ * to this contact.
+ *
+ * @param serverDialog the new clientDialog to use
+ */
+ public void setServerDialog(Dialog serverDialog) {
+ this.serverDialog = serverDialog;
+ }
+
+ /**
+ * Returns the server dialog associated with this contact.
+ * The server dialog is the dialog we use to send our presence status
+ * to this contact.
+ *
+ * @return the clientDialog associated with the contact
+ */
+ public Dialog getServerDialog() {
+ return this.serverDialog;
+ }
/**
* Determines whether or not this contact has been resolved against the
@@ -244,7 +307,7 @@ public String getPersistentData()
*/
public boolean isResolved()
{
- return isResolved;
+ return this.isResolved;
}
/**
@@ -257,6 +320,29 @@ public void setResolved(boolean resolved)
{
this.isResolved = resolved;
}
+
+ /**
+ * Determines whether or not this contact can be resolved against the
+ * server.
+ *
+ * @return true if the contact can be resolved (mapped against a buddy)
+ * and false otherwise.
+ */
+ public boolean isResolvable()
+ {
+ return this.isResolvable;
+ }
+
+ /**
+ * Makes the contact resolvable or unresolvable.
+ *
+ * @param resolvable true to make the contact resolvable; false to
+ * make it unresolvable
+ */
+ public void setResolvable(boolean resolvable)
+ {
+ this.isResolvable = resolvable;
+ }
/**
* Indicates whether some other object is "equal to" this one which in terms
diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java
index 1178acc1f..ac148d626 100644
--- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java
+++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicInstantMessagingSipImpl.java
@@ -46,7 +46,7 @@ public class OperationSetBasicInstantMessagingSipImpl
* A reference to the persistent presence operation set that we use
* to match incoming messages to Contacts and vice versa.
*/
- private OperationSetPersistentPresenceSipImpl opSetPersPresence = null;
+ private OperationSetPresenceSipImpl opSetPersPresence = null;
/**
* Hashtable containing the CSeq of each discussion
@@ -576,7 +576,7 @@ public void registrationStateChanged(RegistrationStateChangeEvent evt)
if (evt.getNewState() == RegistrationState.REGISTERED)
{
- opSetPersPresence = (OperationSetPersistentPresenceSipImpl)
+ opSetPersPresence = (OperationSetPresenceSipImpl)
sipProvider.getSupportedOperationSets()
.get(OperationSetPersistentPresence.class.getName());
}
diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java
new file mode 100644
index 000000000..7c22146bf
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetPresenceSipImpl.java
@@ -0,0 +1,3505 @@
+/*
+ * 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.*;
+import java.text.*;
+import java.util.*;
+import java.io.*;
+
+import javax.sip.*;
+import javax.sip.address.*;
+import javax.sip.header.*;
+import javax.sip.message.*;
+import javax.xml.parsers.*;
+import javax.xml.transform.*;
+import javax.xml.transform.dom.*;
+import javax.xml.transform.stream.*;
+
+import org.w3c.dom.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.service.protocol.jabberconstants.JabberStatusEnum;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Sip presence implementation (SIMPLE).
+ *
+ * Compliant with rfc3261, rfc3265, rfc3856, rfc3863 and rfc3903
+ *
+ * @author Benoit Pradelle
+ */
+public class OperationSetPresenceSipImpl
+ implements OperationSetPersistentPresence, SipListener
+{
+ private static final Logger logger =
+ Logger.getLogger(OperationSetPersistentPresenceSipImpl.class);
+ /**
+ * A list of listeners registered for SubscriptionEvents.
+ */
+ private Vector subscriptionListeners = new Vector();
+
+ /**
+ * A list of listeners registered for
+ * ProviderPresenceStatusChangeEvents.
+ */
+ private Vector providerPresenceStatusListeners = new Vector();
+
+ /**
+ * A list of listeners registered for
+ * ServerStoredGroupChangeEvents.
+ */
+ private Vector serverStoredGroupListeners = new Vector();
+
+ /**
+ * A list of listeners registered for
+ * ContactPresenceStatusChangeEvents.
+ */
+ private Vector contactPresenceStatusListeners = new Vector();
+
+ /**
+ * The root of the SIP contact list.
+ */
+ private ContactGroupSipImpl contactListRoot = null;
+
+ /**
+ * The provider that created us.
+ */
+ private ProtocolProviderServiceSipImpl parentProvider = null;
+
+ /**
+ * The currently active status message.
+ */
+ private String statusMessage = "Default Status Message";
+
+ /**
+ * Our default presence status.
+ */
+ private PresenceStatus presenceStatus = SipStatusEnum.OFFLINE;
+
+ /**
+ * The AuthorizationHandler instance that we'd have to transmit
+ * authorization requests to for approval.
+ */
+ private AuthorizationHandler authorizationHandler = null;
+
+ /**
+ * Hashtable which contains the contacts with which we want to subscribe
+ * or with which we successfuly subscribed
+ * Index : String, Content : ContactSipImpl
+ */
+ private Hashtable subscribedContacts = null;
+
+ /**
+ * List of all the contact interested by our presence status
+ * Content : ContactSipImpl
+ */
+ private Vector ourWatchers = null;
+
+ /**
+ * List of all the CallIds to wait before unregister
+ * Content : String
+ */
+ private Vector waitedCallIds = null;
+
+ /**
+ * Do we have to use a distant presence agent
+ */
+ private boolean useDistantPA = false;
+
+ /**
+ * Entity tag associated with the current communication with the distant PA
+ */
+ private String distantPAET = null;
+
+ /**
+ * the default expiration value of a PUBLISH request
+ */
+ private static final int PUBLISH_DEFAULT_EXPIRE = 600;
+
+ /**
+ * the default expiration value of a SUBSCRIBE request
+ */
+ private static final int SUBSCRIBE_DEFAULT_EXPIRE = 600;
+
+ /**
+ * The document builder factory for generating document builders
+ */
+ private DocumentBuilderFactory docBuilderFactory = null;
+
+ /**
+ * The document builder which produce xml documents
+ */
+ private DocumentBuilder docBuilder = null;
+
+ /**
+ * The transformer factory used to create transformer
+ */
+ private TransformerFactory transFactory = null;
+
+ /**
+ * The transformer used to convert XML documents
+ */
+ private Transformer transformer = null;
+
+ /**
+ * The id used in
+ * @param contactIdentifier the identifier of the contact whose status we're
+ * interested in.
+ * @return PresenceStatus the PresenceStatus of the specified
+ * contact
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if retrieving
+ * the status fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public PresenceStatus queryContactStatus(String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ return resolveContactID(contactIdentifier).getPresenceStatus();
+ }
+
+ /**
+ * Adds a subscription for the presence status of the contact corresponding
+ * to the specified contactIdentifier. Note that apart from an exception in
+ * the case of an immediate failure, the method won't return any indication
+ * of success or failure. That would happen later on through a
+ * SubscriptionEvent generated by one of the methods of the
+ * SubscriptionListener.
+ * We assume here that the user didn't specify any alternative presence URI
+ * for this contact.
+ *
+ * This subscription is not going to be persistent (as opposed to
+ * subscriptions added from the OperationSetPersistentPresence.subscribe()
+ * method)
+ * @param contactIdentifier the identifier of the contact whose status
+ * updates we are subscribing for.
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if subscribing
+ * fails due to errors experienced during network communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void subscribe(String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ subscribe(this.contactListRoot, contactIdentifier);
+ }
+
+ /**
+ * Adds a subscription for the presence status of the contact corresponding
+ * to the specified contactIdentifier. Note that apart from an exception in
+ * the case of an immediate failure, the method won't return any indication
+ * of success or failure. That would happen later on through a
+ * SubscriptionEvent generated by one of the methods of the
+ * SubscriptionListener.
+ *
+ * @param contactIdentifier the identifier of the contact whose status
+ * updates we are subscribing for.
+ *
+ * @throws OperationFailedException if subscribing fails due to errors
+ * experienced during the contact creation
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void subscribe(ContactGroup parentGroup, String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ logger.debug("let's subscribe " + contactIdentifier);
+
+ //if the contact is already in the contact list and is resolved
+ ContactSipImpl contact = (ContactSipImpl)
+ findContactByID(contactIdentifier);
+
+ if (contact != null && contact.isResolved()) {
+ logger.debug("Contact " + contactIdentifier
+ + " already exists.");
+ throw new OperationFailedException(
+ "Contact " + contactIdentifier + " already exists.",
+ OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS);
+ }
+
+ assertConnected();
+
+ // create the contact
+ contact = new ContactSipImpl(contactIdentifier, this.parentProvider);
+
+ //create the subscription
+ Request subscription;
+ try
+ {
+ subscription = createSubscription(contact,
+ SUBSCRIBE_DEFAULT_EXPIRE);
+ }
+ catch (OperationFailedException ex)
+ {
+ logger.error(
+ "Failed to create the subcription"
+ , ex);
+
+ throw new OperationFailedException(
+ "Failed to create the subscription",
+ OperationFailedException.INTERNAL_ERROR);
+ }
+
+ //Transaction
+ ClientTransaction subscribeTransaction;
+ SipProvider jainSipProvider
+ = this.parentProvider.getDefaultJainSipProvider();
+ try
+ {
+ subscribeTransaction = jainSipProvider
+ .getNewClientTransaction(subscription);
+ }
+ catch (TransactionUnavailableException ex)
+ {
+ logger.error(
+ "Failed to create subscriptionTransaction.\n"
+ + "This is most probably a network connection error."
+ , ex);
+
+ throw new OperationFailedException(
+ "Failed to create the subscription transaction",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+
+ // we register the contact to find him when the OK will arrive
+ CallIdHeader idheader = (CallIdHeader)
+ subscription.getHeader(CallIdHeader.NAME);
+ this.subscribedContacts.put(idheader.getCallId(), contact);
+
+ // send the message
+ try
+ {
+ subscribeTransaction.sendRequest();
+ }
+ catch (SipException ex)
+ {
+ logger.error(
+ "Failed to send the message."
+ , ex);
+
+ // this contact will never been accepted or rejected
+ this.subscribedContacts.remove(idheader.getCallId());
+
+ throw new OperationFailedException(
+ "Failed to send the subscription",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+
+ ((ContactGroupSipImpl) parentGroup).addContact(contact);
+
+ // pretend that the contact is created
+ fireSubscriptionEvent(contact,
+ parentGroup,
+ SubscriptionEvent.SUBSCRIPTION_CREATED);
+ }
+
+ /**
+ * Creates a new SUBSCRIBE message with the provided parameters.
+ *
+ * @param contact The contact concerned by this subscription
+ * @param expires The expires value
+ *
+ * @return a valid sip request reprensenting this message.
+ *
+ * @throws OperationFailedException if the message can't be generated
+ */
+ private Request createSubscription(ContactSipImpl contact, int expires)
+ throws OperationFailedException
+ {
+ // Address
+ InetAddress destinationInetAddress = null;
+ Address toAddress = null;
+ try
+ {
+ toAddress = parseAddressStr(contact.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 ex)
+ {
+ //Shouldn't happen
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the address", ex);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the address"
+ , OperationFailedException.INTERNAL_ERROR
+ , ex);
+ }
+
+ Request req;
+ // Call ID
+ CallIdHeader callIdHeader = this.parentProvider
+ .getDefaultJainSipProvider().getNewCallId();
+
+ //CSeq
+ CSeqHeader cSeqHeader = null;
+ try
+ {
+ cSeqHeader = this.parentProvider.getHeaderFactory()
+ .createCSeqHeader(1l, Request.SUBSCRIBE);
+ }
+ catch (InvalidArgumentException ex)
+ {
+ //Shouldn't happen
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the CSeqHeader", ex);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the CSeqHeader"
+ , OperationFailedException.INTERNAL_ERROR
+ , ex);
+ }
+ catch (ParseException ex)
+ {
+ //shouldn't happen
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the CSeqHeader", ex);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the CSeqHeader"
+ , OperationFailedException.INTERNAL_ERROR
+ , ex);
+ }
+
+ //FromHeader and ToHeader
+ String localTag = ProtocolProviderServiceSipImpl.generateLocalTag();
+ FromHeader fromHeader = null;
+ ToHeader toHeader = null;
+ try
+ {
+ //FromHeader
+ fromHeader = this.parentProvider.getHeaderFactory()
+ .createFromHeader(this.parentProvider.getOurSipAddress()
+ , localTag);
+
+ //ToHeader
+ toHeader = this.parentProvider.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.parentProvider.getLocalViaHeaders(
+ destinationInetAddress,
+ this.parentProvider.getDefaultListeningPoint());
+
+ //MaxForwards
+ MaxForwardsHeader maxForwards = this.parentProvider
+ .getMaxForwardsHeader();
+
+ try
+ {
+ req = this.parentProvider.getMessageFactory().createRequest(
+ toHeader.getAddress().getURI(),
+ Request.SUBSCRIBE,
+ callIdHeader,
+ cSeqHeader,
+ fromHeader,
+ toHeader,
+ viaHeaders,
+ maxForwards);
+ }
+ 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);
+ }
+
+ // Event
+ EventHeader evHeader = null;
+ try {
+ evHeader = this.parentProvider.getHeaderFactory()
+ .createEventHeader("presence");
+ } catch (ParseException e) {
+ //these two should never happen.
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the EventHeader", e);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the EventHeader"
+ , OperationFailedException.INTERNAL_ERROR
+ , e);
+ }
+
+ // Contact
+ ContactHeader contactHeader = this.parentProvider.getContactHeader();
+
+ req.setHeader(evHeader);
+ req.setHeader(contactHeader);
+
+ // Accept
+ AcceptHeader accept = null;
+ try {
+ accept = this.parentProvider.getHeaderFactory()
+ .createAcceptHeader("application", PIDF_XML);
+ } catch (ParseException e) {
+ logger.error("wrong accept header");
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the AcceptHeader",
+ OperationFailedException.INTERNAL_ERROR,
+ e);
+ }
+ req.setHeader(accept);
+
+ // Expires
+ ExpiresHeader expHeader = null;
+ try {
+ expHeader = this.parentProvider.getHeaderFactory()
+ .createExpiresHeader(expires);
+ } catch (InvalidArgumentException e) {
+ logger.debug("Invalid expires value: " + expires, e);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the ExpiresHeader",
+ OperationFailedException.INTERNAL_ERROR,
+ e);
+ }
+
+ req.setHeader(expHeader);
+
+ return req;
+ }
+
+ /**
+ * Creates a new SUBSCRIBE message with the provided parameters.
+ *
+ * @param contact The contact concerned by this subscription
+ * @param expires The expires value
+ * @param dialog The dialog with which this request should be associated
+ * or null if this request has to create a new dialog
+ *
+ * @return a ClientTransaction which may be used with
+ * dialog.sendRequest(ClientTransaction) for send the request.
+ *
+ * @throws OperationFailedException if the message can't be generated
+ */
+ private ClientTransaction createSubscription(int expires, Dialog dialog)
+ throws OperationFailedException
+ {
+ Request req = null;
+ try {
+ req = dialog.createRequest(Request.SUBSCRIBE);
+ } catch (SipException e) {
+ logger.debug("Can't create the SUBSCRIBE message");
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the SUBSCRIBE request"
+ , OperationFailedException.INTERNAL_ERROR
+ , e);
+ }
+
+ // Address
+ InetAddress destinationInetAddress = null;
+ Address toAddress = dialog.getRemoteTarget();
+
+ // no Contact field
+ if (toAddress == null) {
+ toAddress = dialog.getRemoteParty();
+ }
+
+ try
+ {
+ 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());
+ }
+
+ //MaxForwards
+ MaxForwardsHeader maxForwards = this.parentProvider
+ .getMaxForwardsHeader();
+
+ // EventHeader
+ EventHeader evHeader = null;
+ try {
+ evHeader = this.parentProvider.getHeaderFactory()
+ .createEventHeader("presence");
+ } catch (ParseException e) {
+ //these two should never happen.
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the EventHeader", e);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the EventHeader"
+ , OperationFailedException.INTERNAL_ERROR
+ , e);
+ }
+
+ // Contact
+ ContactHeader contactHeader = this.parentProvider
+ .getContactHeader();
+
+ // Accept
+ AcceptHeader accept = null;
+ try {
+ accept = this.parentProvider.getHeaderFactory()
+ .createAcceptHeader("application", PIDF_XML);
+ } catch (ParseException e) {
+ logger.error("wrong accept header");
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the AcceptHeader",
+ OperationFailedException.INTERNAL_ERROR,
+ e);
+ }
+
+ // Expires
+ ExpiresHeader expHeader = null;
+ try {
+ expHeader = this.parentProvider.getHeaderFactory()
+ .createExpiresHeader(expires);
+ } catch (InvalidArgumentException e) {
+ logger.debug("Invalid expires value: " + expires, e);
+ throw new OperationFailedException(
+ "An unexpected error occurred while"
+ + "constructing the ExpiresHeader",
+ OperationFailedException.INTERNAL_ERROR,
+ e);
+ }
+
+ req.setHeader(expHeader);
+ req.setHeader(accept);
+ req.setHeader(maxForwards);
+ req.setHeader(evHeader);
+ req.setHeader(contactHeader);
+
+ // create the transaction (then add the via header as recommended
+ // by the jain-sip documentation at:
+ // http://snad.ncsl.nist.gov/proj/iptel/jain-sip-1.2
+ // /javadoc/javax/sip/Dialog.html#createRequest(java.lang.String)
+ ClientTransaction transac = null;
+ try
+ {
+ transac = this.parentProvider.getDefaultJainSipProvider()
+ .getNewClientTransaction(req);
+ }
+ catch (TransactionUnavailableException ex)
+ {
+ logger.error(
+ "Failed to create subscriptionTransaction.\n"
+ + "This is most probably a network connection error."
+ , ex);
+
+ throw new OperationFailedException(
+ "Failed to create the subscription transaction",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+
+ //ViaHeaders
+ ArrayList viaHeaders = this.parentProvider.getLocalViaHeaders(
+ destinationInetAddress
+ , this.parentProvider.getDefaultListeningPoint());
+ req.addHeader((Header) viaHeaders.get(0));
+
+ return transac;
+ }
+
+ /**
+ * Parses the the uriStr string and returns a JAIN SIP URI.
+ *
+ * @param uriStr a String containing the uri to parse.
+ *
+ * @return a URI object corresponding to the uriStr string.
+ * @throws ParseException if uriStr is not properly formatted.
+ */
+ private Address parseAddressStr(String uriStr)
+ throws ParseException
+ {
+ String res = uriStr.trim();
+
+ //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 (res.indexOf('@') == -1)
+ {
+ res = res + '@'
+ + ((SipURI) this.parentProvider.getOurSipAddress().getURI())
+ .getHost();
+ }
+
+ //Let's be uri fault tolerant and add the sip: scheme if there is none.
+ if (!res.toLowerCase().startsWith("sip:") //no sip scheme
+ && !res.toLowerCase().startsWith("pres:"))
+ {
+ res = "sip:" + res; //most probably a sip uri
+ }
+
+ //Request URI
+ Address uri
+ = this.parentProvider.getAddressFactory().createAddress(res);
+
+ return uri;
+ }
+
+ /**
+ * 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.parentProvider == null)
+ throw new IllegalStateException(
+ "The provider must be non-null and signed on the "
+ + "service before being able to communicate.");
+ if (!this.parentProvider.isRegistered())
+ throw new IllegalStateException(
+ "The provider must be signed on the service before "
+ + "being able to communicate.");
+ }
+
+ /**
+ * Removes a subscription for the presence status of the specified contact.
+ * @param contact the contact whose status updates we are unsubscribing
+ * from.
+ *
+ * @throws OperationFailedException with code NETWORK_FAILURE if
+ * unsubscribing fails due to errors experienced during network
+ * communication
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is not
+ * registered/signed on a public service.
+ */
+ public void unsubscribe(Contact contact)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ if (!(contact instanceof ContactSipImpl)) {
+ throw new IllegalArgumentException("the contact is not a SIP" +
+ " contact");
+ }
+
+ ContactSipImpl sipcontact = (ContactSipImpl) contact;
+
+ // handle the case of a distant presence agent is used
+ if (this.useDistantPA) {
+ // simply send a notify with an expire to 0
+ Request req = createPublish(0);
+
+ ClientTransaction transac = null;
+ try {
+ transac = this.parentProvider
+ .getDefaultJainSipProvider().getNewClientTransaction(req);
+ } catch (TransactionUnavailableException e) {
+ logger.debug("can't create the client transaction", e);
+ throw new OperationFailedException(
+ "can't create the client transaction",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+
+ try {
+ transac.sendRequest();
+ } catch (SipException e) {
+ logger.debug("can't send the PUBLISH request");
+ throw new OperationFailedException(
+ "can't send the PUBLISH request",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+
+ this.distantPAET = null;
+ return;
+ }
+
+ Dialog dialog = sipcontact.getClientDialog();
+
+ // check if we heard about this contact
+ if (this.subscribedContacts.get(dialog.getCallId().getCallId())
+ == null)
+ {
+ throw new IllegalArgumentException("trying to unregister a not " +
+ "registered contact");
+ }
+
+ // we stop the subscribtion if we're subscribed to this contact
+ if (!contact.getPresenceStatus().equals(SipStatusEnum.OFFLINE)
+ && !contact.getPresenceStatus().equals(SipStatusEnum.UNKNOWN)
+ && sipcontact.isResolvable())
+ {
+ assertConnected();
+
+ ClientTransaction transac = null;
+ try {
+ transac = createSubscription(0, dialog);
+ } catch (OperationFailedException e) {
+ logger.debug("failed to create the unsubscription", e);
+ throw e;
+ }
+
+ try {
+ dialog.sendRequest(transac);
+ } catch (Exception e) {
+ logger.debug("Can't send the request");
+ throw new OperationFailedException(
+ "Failed to send the subscription message",
+ OperationFailedException.NETWORK_FAILURE);
+ }
+ }
+
+ // remove any trace of this contact
+ terminateSubscription(sipcontact);
+ this.subscribedContacts.remove(dialog.getCallId().getCallId());
+ ((ContactGroupSipImpl) sipcontact.getParentContactGroup())
+ .removeContact(sipcontact);
+
+ // inform the listeners
+ fireSubscriptionEvent(sipcontact,
+ sipcontact.getParentContactGroup(),
+ SubscriptionEvent.SUBSCRIPTION_REMOVED);
+ }
+
+ /**
+ * Analyzes the incoming responseEvent and then forwards it to the
+ * proper event handler.
+ *
+ * @param responseEvent the responseEvent that we received
+ * ProtocolProviderService.
+ */
+ public void processResponse(ResponseEvent responseEvent)
+ {
+ ClientTransaction clientTransaction = responseEvent
+ .getClientTransaction();
+ Response response = responseEvent.getResponse();
+
+ CSeqHeader cseq = ((CSeqHeader)response.getHeader(CSeqHeader.NAME));
+ if (cseq == null)
+ {
+ logger.error("An incoming response did not contain a CSeq header");
+ return;
+ }
+ String method = cseq.getMethod();
+
+ SipProvider sourceProvider = (SipProvider)responseEvent.getSource();
+
+ // SUBSCRIBE
+ if (method.equals(Request.SUBSCRIBE)) {
+ // find the contact
+ CallIdHeader idheader = (CallIdHeader)
+ response.getHeader(CallIdHeader.NAME);
+ ContactSipImpl contact = (ContactSipImpl) this.subscribedContacts
+ .get(idheader.getCallId());
+
+ // if it's the response to an unsubscribe message, we just ignore it
+ // whatever the response is however if we need to handle a
+ // challenge, we do it
+ ExpiresHeader expHeader = (ExpiresHeader)
+ response.getHeader(ExpiresHeader.NAME);
+ if ((expHeader != null && expHeader.getExpires() == 0)
+ || contact == null)
+ {
+ if (response.getStatusCode() == Response.UNAUTHORIZED
+ || response.getStatusCode() ==
+ Response.PROXY_AUTHENTICATION_REQUIRED)
+ {
+ try {
+ processAuthenticationChallenge(clientTransaction,
+ response, sourceProvider);
+ } catch (OperationFailedException e) {
+ logger.error("can't handle the challenge");
+ }
+ } else if (response.getStatusCode() != Response.OK
+ && response.getStatusCode() != Response.ACCEPTED)
+ {
+ // this definitivly ends the subscription
+ synchronized (this.waitedCallIds) {
+ this.waitedCallIds.remove(idheader.getCallId());
+ }
+ }
+ // any other case (200/202) will imply a NOTIFY, so we will
+ // handle the end of a subscription there
+
+ return;
+ }
+
+ try {
+ finalizeSubscription(contact,
+ clientTransaction.getDialog());
+ } catch (NullPointerException e) {
+ // should not happen
+ logger.debug("failed to finalize the subscription of the" +
+ "contact", e);
+
+ return;
+ }
+
+ // OK (200/202)
+ if (response.getStatusCode() == Response.OK
+ || response.getStatusCode() == Response.ACCEPTED)
+ {
+ // just wait the notify which will set the contact status
+ // UNAUTHORIZED (401/407)
+ } else if (response.getStatusCode() == Response.UNAUTHORIZED
+ || response.getStatusCode() == Response
+ .PROXY_AUTHENTICATION_REQUIRED)
+ {
+ try {
+ processAuthenticationChallenge(clientTransaction,
+ response, sourceProvider);
+ } catch (OperationFailedException e) {
+ logger.error("can't handle the challenge");
+
+ // we probably won't be able to communicate with the contact
+ changePresenceStatusForContact(contact,
+ SipStatusEnum.UNKNOWN);
+ }
+ // 408 480 486 600 603 : non definitive reject
+ } else if (response.getStatusCode() == Response.REQUEST_TIMEOUT
+ || response.getStatusCode() == Response
+ .TEMPORARILY_UNAVAILABLE
+ || response.getStatusCode() == Response.BUSY_HERE
+ || response.getStatusCode() == Response.BUSY_EVERYWHERE
+ || response.getStatusCode() == Response.DECLINE)
+ {
+ logger.debug("error received from the network" + response);
+
+ if (response.getStatusCode() == Response
+ .TEMPORARILY_UNAVAILABLE)
+ {
+ changePresenceStatusForContact(contact,
+ SipStatusEnum.OFFLINE);
+ } else {
+ changePresenceStatusForContact(contact,
+ SipStatusEnum.UNKNOWN);
+ }
+ // definitive reject (or not implemented)
+ } else {
+ logger.debug("error received from the network" + response);
+
+ // we'll never be able to resolve this contact
+ contact.setResolvable(false);
+ changePresenceStatusForContact(contact, SipStatusEnum.UNKNOWN);
+ }
+
+ // NOTIFY
+ } else if (method.equals(Request.NOTIFY)) {
+ // if it's a final response to a NOTIFY, we try to remove it from
+ // the list of waited NOTIFY end
+ if (response.getStatusCode() != Response.UNAUTHORIZED
+ && response.getStatusCode() != Response
+ .PROXY_AUTHENTICATION_REQUIRED)
+ {
+ synchronized (this.waitedCallIds) {
+ this.waitedCallIds.remove(((CallIdHeader) response
+ .getHeader(CallIdHeader.NAME)).getCallId());
+ }
+ }
+
+ // OK (200)
+ if (response.getStatusCode() == Response.OK) {
+ // simply nothing to do here, the contact received our NOTIFY,
+ // everything is ok
+ // UNAUTHORIZED (401/407)
+ } else if (response.getStatusCode() == Response.UNAUTHORIZED
+ || response.getStatusCode() == Response
+ .PROXY_AUTHENTICATION_REQUIRED)
+ {
+ try {
+ processAuthenticationChallenge(clientTransaction,
+ response, sourceProvider);
+ } catch (OperationFailedException e) {
+ logger.error("can't handle the challenge");
+
+ // don't try to tell him anything more
+ String contactAddress = ((FromHeader)
+ response.getHeader(FromHeader.NAME)).getAddress()
+ .getURI().toString();
+ Contact watcher = getWatcher(contactAddress);
+
+ if (watcher != null) {
+ synchronized (this.ourWatchers) {
+ this.ourWatchers.remove(watcher);
+ }
+ }
+ }
+ // every error cause the subscription to be removed
+ // as recommended for some cases in rfc3265
+ } else {
+ logger.debug("error received from the network" + response);
+
+ String contactAddress = ((FromHeader)
+ response.getHeader(FromHeader.NAME)).getAddress()
+ .getURI().toString();
+ Contact watcher = getWatcher(contactAddress);
+
+ if (watcher != null) {
+ synchronized (this.ourWatchers) {
+ this.ourWatchers.remove(watcher);
+ }
+ }
+ }
+
+ // PUBLISH
+ } else if (method.equals(Request.PUBLISH)) {
+ // if it's a final response to a PUBLISH, we try to remove it from
+ // the list of waited PUBLISH end
+ if (response.getStatusCode() != Response.UNAUTHORIZED
+ && response.getStatusCode() != Response
+ .PROXY_AUTHENTICATION_REQUIRED)
+ {
+ synchronized (this.waitedCallIds) {
+ this.waitedCallIds.remove(((CallIdHeader) response
+ .getHeader(CallIdHeader.NAME)).getCallId());
+ }
+ }
+
+ // OK (200)
+ if (response.getStatusCode() == Response.OK) {
+ // remember the entity tag
+ SIPETagHeader etHeader = (SIPETagHeader)
+ response.getHeader(SIPETagHeader.NAME);
+
+ if (etHeader == null) {
+ logger.debug("can't find the ETag header");
+ return;
+ }
+
+ this.distantPAET = etHeader.getETag();
+
+ // UNAUTHORIZED (401/407)
+ } else if (response.getStatusCode() == Response.UNAUTHORIZED
+ || response.getStatusCode() == Response
+ .PROXY_AUTHENTICATION_REQUIRED)
+ {
+ try {
+ processAuthenticationChallenge(clientTransaction,
+ response, sourceProvider);
+ } catch (OperationFailedException e) {
+ logger.error("can't handle the challenge");
+ return;
+ }
+ // with every other error, we consider that we have to start a new
+ // communication
+ } else {
+ logger.debug("error received from the network" + response);
+ this.distantPAET = null;
+ }
+ }
+ }
+
+ /**
+ * Finalize the subscription of a contact and transform the pending contact
+ * into a real contact.
+ *
+ * @param contact the contact concerned
+ * @param dialog the dialog which will be used to communicate with this
+ * contact for retrieving its status
+ *
+ * @throws NullPointerException if dialog or contact is null
+ */
+ private void finalizeSubscription(ContactSipImpl contact, Dialog dialog)
+ throws NullPointerException
+ {
+ // remember the dialog created to be able to send SUBSCRIBE
+ // refresh and to unsibscribe
+ if (dialog == null) {
+ throw new NullPointerException("null dialog associated with a " +
+ "contact: " + contact);
+ }
+ if (contact == null) {
+ throw new NullPointerException("null contact");
+ }
+
+ // set the contact client dialog
+ contact.setClientDialog(dialog);
+
+ contact.setResolved(true);
+
+ // inform the listeners that the contact is created
+ this.fireSubscriptionEvent(contact,
+ contact.getParentContactGroup(),
+ SubscriptionEvent.SUBSCRIPTION_RESOLVED);
+
+ logger.debug("contact : " + contact + " resolved");
+ }
+
+ /**
+ * Terminate the subscription to a contact presence status
+ *
+ * @param contact the contact concerned
+ */
+ private void terminateSubscription(ContactSipImpl contact) {
+ if (contact == null) {
+ logger.debug("null contact provided, can't terminate" +
+ " subscription");
+ return;
+ }
+
+ contact.setClientDialog(null);
+
+ // we don't remove the contact as it may just be a network problem
+ changePresenceStatusForContact(contact, SipStatusEnum.UNKNOWN);
+ contact.setResolved(false);
+ }
+
+ /**
+ * Creates a NOTIFY request corresponding to the provided arguments.
+ * This request MUST be sent using dialog.sendRequest
+ *
+ * @param contact The contact to notify
+ * @param doc The presence document to send
+ * @param subscriptionState The current subscription state
+ * @param reason The reason of this subscription state (may be null)
+ *
+ * @return a valid ClientTransaction ready to send the request
+ *
+ * @throws OperationFailedException if something goes wrong during the
+ * creation of the request
+ */
+ private ClientTransaction createNotify(ContactSipImpl contact, byte[] doc,
+ String subscriptionState, String reason)
+ throws OperationFailedException
+ {
+ Dialog dialog = contact.getServerDialog();
+
+ if (dialog == null) {
+ throw new OperationFailedException("the server dialog of the " +
+ "contact is null", OperationFailedException.INTERNAL_ERROR);
+ }
+
+ Request req = null;
+ try {
+ req = dialog.createRequest(Request.NOTIFY);
+ } catch (SipException e) {
+ logger.debug("Can't create the NOTIFY message");
+ throw new OperationFailedException("Can't create the NOTIFY" +
+ " message", OperationFailedException.INTERNAL_ERROR, e);
+ }
+
+ // Address
+ InetAddress destinationInetAddress = null;
+ Address toAddress = dialog.getRemoteTarget();
+
+ // no Contact field
+ if (toAddress == null) {
+ toAddress = dialog.getRemoteParty();
+ }
+
+ try
+ {
+ destinationInetAddress = InetAddress.getByName(
+ ((SipURI) toAddress.getURI()).getHost());
+ }
+ catch (UnknownHostException ex)
+ {
+ throw new OperationFailedException(
+ ((SipURI) toAddress.getURI()).getHost()
+ + " is not a valid internet address ",
+ OperationFailedException.INTERNAL_ERROR, ex);
+ }
+
+ ArrayList viaHeaders = null;
+ MaxForwardsHeader maxForwards = null;
+
+ try {
+ //ViaHeaders
+ viaHeaders = this.parentProvider.getLocalViaHeaders(
+ destinationInetAddress
+ , this.parentProvider.getDefaultListeningPoint());
+
+ //MaxForwards
+ maxForwards = this.parentProvider
+ .getMaxForwardsHeader();
+ } catch (OperationFailedException e) {
+ logger.debug("cant retrive the via headers or the max forward",
+ e);
+ throw new OperationFailedException("Can't create the NOTIFY" +
+ " message", OperationFailedException.INTERNAL_ERROR);
+ }
+
+ EventHeader evHeader = null;
+ try {
+ evHeader = this.parentProvider.getHeaderFactory()
+ .createEventHeader("presence");
+ } catch (ParseException e) {
+ //these two should never happen.
+ logger.error(
+ "An unexpected error occurred while"
+ + "constructing the EventHeader", e);
+ throw new OperationFailedException("Can't create the Event" +
+ " header", OperationFailedException.INTERNAL_ERROR, e);
+ }
+
+ // Contact
+ ContactHeader contactHeader = this.parentProvider
+ .getContactHeader();
+
+ // Subscription-State
+ SubscriptionStateHeader sStateHeader = null;
+ try {
+ sStateHeader = this.parentProvider
+ .getHeaderFactory().createSubscriptionStateHeader(
+ subscriptionState);
+
+ if (reason != null && !reason.trim().equals("")) {
+ sStateHeader.setReasonCode(reason);
+ }
+ } catch (ParseException e) {
+ // should never happen
+ logger.debug("can't create the Subscription-State header", e);
+ throw new OperationFailedException("Can't create the " +
+ "Subscription-State header",
+ OperationFailedException.INTERNAL_ERROR, e);
+ }
+
+ // Content-type
+ ContentTypeHeader cTypeHeader = null;
+ try {
+ cTypeHeader = this.parentProvider
+ .getHeaderFactory().createContentTypeHeader("application",
+ PIDF_XML);
+ } catch (ParseException e) {
+ // should never happen
+ logger.debug("can't create the Content-Type header", e);
+ throw new OperationFailedException("Can't create the " +
+ "Content-type header",
+ OperationFailedException.INTERNAL_ERROR, e);
+ }
+
+ req.setHeader(maxForwards);
+ req.setHeader(evHeader);
+ req.setHeader(sStateHeader);
+ req.setHeader(contactHeader);
+
+ // create the transaction (then add the via header as recommended
+ // by the jain-sip documentation at:
+ // http://snad.ncsl.nist.gov/proj/iptel/jain-sip-1.2
+ // /javadoc/javax/sip/Dialog.html#createRequest(java.lang.String)
+ ClientTransaction transac = null;
+ try
+ {
+ transac = this.parentProvider.getDefaultJainSipProvider()
+ .getNewClientTransaction(req);
+ }
+ catch (TransactionUnavailableException ex)
+ {
+ logger.error(
+ "Failed to create subscriptionTransaction.\n"
+ + "This is most probably a network connection error."
+ , ex);
+
+ throw new OperationFailedException("Can't create the " +
+ "Content-length header",
+ OperationFailedException.NETWORK_FAILURE, ex);
+ }
+
+ req.addHeader((Header) viaHeaders.get(0));
+
+ // add the content
+ try {
+ req.setContent(doc, cTypeHeader);
+ } catch (ParseException e) {
+ logger.debug("Failed to add the presence document", e);
+ throw new OperationFailedException("Can't add the presence " +
+ "document to the request",
+ OperationFailedException.INTERNAL_ERROR, e);
+ }
+
+ return transac;
+ }
+
+ /**
+ * Process a request from a distant contact
+ *
+ * @param requestEvent the RequestEvent containing the newly
+ * received request.
+ */
+ public void processRequest(RequestEvent requestEvent)
+ {
+ ServerTransaction serverTransaction = requestEvent
+ .getServerTransaction();
+ SipProvider jainSipProvider = (SipProvider) requestEvent.getSource();
+ Request request = requestEvent.getRequest();
+
+ if (serverTransaction == null)
+ {
+ try
+ {
+ serverTransaction = jainSipProvider.getNewServerTransaction(
+ request);
+ }
+ 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;
+ }
+ }
+
+ EventHeader eventHeader = (EventHeader)
+ request.getHeader(EventHeader.NAME);
+
+ if (eventHeader == null || !eventHeader.getEventType()
+ .equalsIgnoreCase("presence"))
+ {
+ // we are not concerned by this request, perhaps another
+ // listener is ?
+ return;
+ }
+
+
+ // NOTIFY
+ if (request.getMethod().equals(Request.NOTIFY)) {
+ Response response = null;
+
+ logger.debug("notify received");
+
+ SubscriptionStateHeader sstateHeader = (SubscriptionStateHeader)
+ request.getHeader(SubscriptionStateHeader.NAME);
+
+ // notify must contain one (rfc3265)
+ if (sstateHeader == null) {
+ logger.error("no subscription state in this request");
+ return;
+ }
+
+ // first try to accept the contact if the contact is pending
+ // it's possible if the NOTIFY arrives before the OK
+ CallIdHeader idheader = (CallIdHeader) request.getHeader(
+ CallIdHeader.NAME);
+ ContactSipImpl contact = (ContactSipImpl) this.subscribedContacts
+ .get(idheader.getCallId());
+
+ if (contact != null && !sstateHeader.getState().equalsIgnoreCase(
+ SubscriptionStateHeader.TERMINATED) && !contact
+ .isResolved())
+ {
+ logger.debug("contact still pending while NOTIFY received");
+ try {
+ finalizeSubscription(contact,
+ serverTransaction.getDialog());
+ } catch (NullPointerException e) {
+ logger.debug("failed to finalize the subscription of the" +
+ "contact", e);
+ return;
+ }
+ }
+
+ // see if the notify correspond to an existing subscription
+ if (contact == null) {
+ logger.debug("contact not found for callid : " +
+ idheader.getCallId());
+
+ // try to remove the callid from the list if we were excpeting
+ // this end (if it's the last notify of a subscription we just
+ // stopped
+ synchronized (this.waitedCallIds) {
+ this.waitedCallIds.remove(idheader.getCallId());
+ }
+
+ // send a 481 response (rfc3625)
+ try {
+ response = this.parentProvider.getMessageFactory()
+ .createResponse(
+ Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST,
+ request);
+ } catch (ParseException e) {
+ logger.debug("failed to create the 481 response", e);
+ return;
+ }
+
+ try {
+ serverTransaction.sendResponse(response);
+ } catch (SipException e) {
+ logger.debug("failed to send the response", e);
+ } catch (InvalidArgumentException e) {
+ // should not happen
+ logger.debug("invalid argument provided while trying" +
+ " to send the response", e);
+ }
+
+ return;
+ }
+
+ // if we don't understand the content
+ ContentTypeHeader ctheader = (ContentTypeHeader) request
+ .getHeader(ContentTypeHeader.NAME);
+
+ if (ctheader != null && !ctheader.getContentSubType()
+ .equalsIgnoreCase(PIDF_XML))
+ {
+ // send a 415 response (rfc3261)
+ try {
+ response = this.parentProvider.getMessageFactory()
+ .createResponse(Response.UNSUPPORTED_MEDIA_TYPE,
+ request);
+ } catch (ParseException e) {
+ logger.debug("failed to create the OK response", e);
+ return;
+ }
+
+ // we want PIDF
+ AcceptHeader acceptHeader = null;
+ try {
+ acceptHeader = this.parentProvider
+ .getHeaderFactory().createAcceptHeader(
+ "application", PIDF_XML);
+ } catch (ParseException e) {
+ // should not happen
+ logger.debug("failed to create the accept header", e);
+ return;
+ }
+ response.setHeader(acceptHeader);
+
+ try {
+ serverTransaction.sendResponse(response);
+ } catch (SipException e) {
+ logger.debug("failed to send the response", e);
+ } catch (InvalidArgumentException e) {
+ // should not happen
+ logger.debug("invalid argument provided while trying" +
+ " to send the response", e);
+ }
+ }
+
+ // send an OK response
+ try {
+ response = this.parentProvider.getMessageFactory()
+ .createResponse(Response.OK, request);
+ } catch (ParseException e) {
+ logger.debug("failed to create the OK response", e);
+ return;
+ }
+
+ try {
+ serverTransaction.sendResponse(response);
+ } catch (SipException e) {
+ logger.debug("failed to send the response", e);
+ } catch (InvalidArgumentException e) {
+ // should not happen
+ logger.debug("invalid argument provided while trying" +
+ " to send the response", e);
+ }
+
+ // if the presentity doesn't want of us anymore
+ if (sstateHeader.getState().equalsIgnoreCase(
+ SubscriptionStateHeader.TERMINATED))
+ {
+ terminateSubscription(contact);
+ this.subscribedContacts.remove(serverTransaction.getDialog()
+ .getCallId().getCallId());
+
+ // try to remove the callid from the list if we were excpeting
+ // this end (if it's the last notify of a subscription we just
+ // stopped
+ synchronized (this.waitedCallIds) {
+ this.waitedCallIds.remove(idheader.getCallId());
+ }
+ }
+ // transform the presence document in new presence status
+ if (request.getRawContent() != null) {
+ setPidfPresenceStatus(new String(request.getRawContent()));
+ }
+
+ // SUBSCRIBE
+ } else if (request.getMethod().equals(Request.SUBSCRIBE)) {
+ FromHeader from = (FromHeader) request.getHeader(FromHeader.NAME);
+
+ // try to find which contact is concerned
+ ContactSipImpl contact = (ContactSipImpl) resolveContactID(from
+ .getAddress().getURI().toString());
+
+ // if we don't know him, create him
+ if (contact == null) {
+ contact = new ContactSipImpl(from.getAddress().getURI()
+ .toString(), this.parentProvider);
+
+ //