/*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.mobicents.servlet.sip.core.dispatchers;
import gov.nist.javax.sip.ServerTransactionExt;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.sip.Proxy;
import javax.servlet.sip.SipSession.State;
import javax.sip.InvalidArgumentException;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.Transaction;
import javax.sip.message.Request;
import javax.sip.message.Response;
import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.core.RoutingState;
import org.mobicents.servlet.sip.core.SipApplicationDispatcherImpl;
import org.mobicents.servlet.sip.core.session.MobicentsSipSession;
import org.mobicents.servlet.sip.message.SipServletMessageImpl;
import org.mobicents.servlet.sip.message.SipServletRequestImpl;
import org.mobicents.servlet.sip.message.SipServletResponseImpl;
import org.mobicents.servlet.sip.message.TransactionApplicationData;
/**
* <p>
* This class implements the logic for routing CANCEL requests along the
* application path followed by the INVITE. <br/> CANCEL deserves a special
* treatment since they are routed hop by hop.
* </p>
*
* <p>
* Algorithm used : We can distinguish 2 cases here as per spec :
* <ul>
* <li>
* Applications that acts as User Agent 11.2.3 Receiving CANCEL :
*
* When a CANCEL is received for a request which has been passed to an
* application, and the application has not responded yet or proxied the
* original request, the container responds to the original request with a 487
* (Request Terminated) and to the CANCEL with a 200 OK final response, and it
* notifies the application by passing it a SipServletRequest object
* representing the CANCEL request. The application should not attempt to
* respond to a request after receiving a CANCEL for it. Neither should it
* respond to the CANCEL notification. Clearly, there is a race condition
* between the container generating the 487 response and the SIP servlet
* generating its own response. This should be handled using standard Java
* mechanisms for resolving race conditions. If the application wins, it will
* not be notified that a CANCEL request was received. If the container wins and
* the servlet tries to send a response before (or for that matter after) being
* notified of the CANCEL, the container throws an IllegalStateException.</li>
*
* <li>
* Applications that acts as proxy :
*
* 10.2.6 Receiving CANCEL if the original
* request has not been proxied yet the container responds to it with a 487
* final response otherwise, all branches are cancelled, and response processing
* continues as usual In either case, the application is subsequently invoked
* with the CANCEL request. This is a notification only, as the server has
* already responded to the CANCEL and cancelled outstanding branches as
* appropriate. The race condition between the server sending the 487 response
* and the application proxying the request is handled as in the UAS case as
* discussed in section 11.2.3 Receiving CANCEL.</li>
* </ul>
* </p>
*
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*
*/
public class CancelRequestDispatcher extends RequestDispatcher {
private static transient Logger logger = Logger.getLogger(CancelRequestDispatcher.class);
public CancelRequestDispatcher() {}
// public CancelRequestDispatcher(
// SipApplicationDispatcher sipApplicationDispatcher) {
// super(sipApplicationDispatcher);
// }
/**
* {@inheritDoc}
*/
public void dispatchMessage(final SipProvider sipProvider, SipServletMessageImpl sipServletMessage) throws DispatcherException {
// final SipNetworkInterfaceManager sipNetworkInterfaceManager = sipApplicationDispatcher.getSipNetworkInterfaceManager();
final SipServletRequestImpl sipServletRequest = (SipServletRequestImpl) sipServletMessage;
if(logger.isInfoEnabled()) {
logger.info("Routing of Cancel Request " + sipServletRequest);
}
/*
* WARNING: routing of CANCEL is special because CANCEL does not contain Route headers as other requests related
* to the dialog. But still it has to be routed through the app path
* of the INVITE
*/
/* If there is a proxy with the request, let's try to send it directly there.
* This is needed because of CANCEL which is a subsequent request that might
* not have Routes. For example if the callee has'n responded the caller still
* doesn't know the route-record and just sends cancel to the outbound proxy.
*/
// boolean proxyCancel = false;
try {
// First we need to send OK ASAP because of retransmissions both for
//proxy or app
ServerTransaction cancelTransaction =
(ServerTransaction) sipServletRequest.getTransaction();
SipServletResponseImpl cancelResponse = (SipServletResponseImpl)
sipServletRequest.createResponse(200, "Canceling");
Response cancelJsipResponse = (Response) cancelResponse.getMessage();
cancelTransaction.sendResponse(cancelJsipResponse);
} catch (SipException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to send the ok to the CANCEL", e);
} catch (InvalidArgumentException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to send the ok to the CANCEL", e);
}
if(logger.isDebugEnabled()) {
logger.debug("checking what to do with the CANCEL " + sipServletRequest);
}
DispatchTask dispatchTask = new CancelDispatchTask(sipServletRequest, sipProvider);
// Execute CANCEL without waiting for previous requests because if we wait for an INVITE to complete
// all responses will be already sent by the time the CANCEL is out of the queue.
((SipApplicationDispatcherImpl)this.sipApplicationDispatcher).getAsynchronousExecutor().execute(dispatchTask);
}
/**
* @param inviteTransaction
* @param inviteRequest
*/
private final static void send487Response(Transaction inviteTransaction, SipServletRequestImpl inviteRequest) throws IllegalStateException, DispatcherException {
SipServletResponseImpl inviteResponse = (SipServletResponseImpl)
inviteRequest.createResponse(Response.REQUEST_TERMINATED);
inviteRequest.setRoutingState(RoutingState.CANCELLED);
//JSR 289 Section 6.2.1.1 Cancel Message Processing : since receiving a CANCEL request causes the UAS
// to respond to an ongoing INVITE transaction with a non-2XX (specifically, 487) response, the SipSession state
// normally becomes TERMINATED as a result of the non-2XX final response sent back to the UAC.
inviteRequest.getSipSession().setState(State.TERMINATED);
try {
Response requestTerminatedResponse = (Response) inviteResponse.getMessage();
((ServerTransaction)inviteTransaction).sendResponse(requestTerminatedResponse);
} catch (SipException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to send the 487 to the INVITE transaction corresponding to CANCEL", e);
} catch (InvalidArgumentException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "Impossible to send the 487 to the INVITE transaction corresponding to CANCEL", e);
}
}
public static class CancelDispatchTask extends DispatchTask {
CancelDispatchTask(SipServletRequestImpl sipServletRequest, SipProvider sipProvider) {
super(sipServletRequest, sipProvider);
}
public void dispatch() throws DispatcherException {
final SipServletRequestImpl sipServletRequest = (SipServletRequestImpl)sipServletMessage;
final Request request = (Request) sipServletRequest.getMessage();
final Transaction inviteTransaction = ((ServerTransactionExt) sipServletRequest.getTransaction()).getCanceledInviteTransaction();
if(inviteTransaction == null) {
logger.error("couldn't find the original invite transaction for this cancel " + request);
return;
}
final TransactionApplicationData inviteAppData = (TransactionApplicationData)inviteTransaction.getApplicationData();
final SipServletRequestImpl inviteRequest = (SipServletRequestImpl)
inviteAppData.getSipServletMessage();
final MobicentsSipSession sipSession = inviteRequest.getSipSession();
sipServletRequest.setSipSessionKey(sipSession.getKey());
if(logger.isDebugEnabled()) {
logger.debug("message associated with the inviteAppData " +
"of the CANCEL " + inviteRequest);
}
if(logger.isDebugEnabled()) {
logger.debug("invite transaction associated with the inviteAppData " +
"of the CANCEL " + inviteTransaction);
}
if(logger.isDebugEnabled()) {
logger.debug("app data of the invite transaction associated with the inviteAppData " +
"of the CANCEL " + inviteAppData);
}
if(logger.isDebugEnabled()) {
logger.debug("routing state of the INVITE request for the CANCEL = " + inviteRequest.getRoutingState());
}
Proxy proxy = sipSession.getProxy();
if(proxy != null) {
if(logger.isDebugEnabled()) {
logger.debug("proxying the CANCEL " + sipServletRequest);
}
// Routing State : PROXY case
if(!RoutingState.PROXIED.equals(inviteRequest.getRoutingState())) {
// 10.2.6 if the original request has not been proxied yet the container
// responds to it with a 487 final response
try {
send487Response(inviteTransaction, inviteRequest);
} catch(IllegalStateException iae) {
logger.info("request already proxied, dropping the cancel");
return;
}
} else {
// otherwise, all branches are cancelled, and response processing continues as usual
proxy.cancel();
}
// Fix for Issue 796 : SIP servlet (simple proxy) does not receive "Cancel" requests. (http://code.google.com/p/mobicents/issues/detail?id=796)
// JSR 289 Section 10.2.6 Receiving CANCEL : In either case, the application is subsequently invoked with the CANCEL request
try{
callServlet(sipServletRequest);
} catch (ServletException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected servlet exception occured while routing the following CANCEL " + request, e);
} catch (IOException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected IO exception occured while routing the following CANCEL " + request, e);
}
} else if(RoutingState.FINAL_RESPONSE_SENT.equals(inviteRequest.getRoutingState())) {
if(logger.isDebugEnabled()) {
logger.debug("the final response has already been sent, nothing to do here");
}
} else {
if(logger.isDebugEnabled()) {
logger.debug("invite transaction of the CANCEL " + inviteTransaction);
}
if(logger.isDebugEnabled()) {
logger.debug("invite message : 1xx response was generated ? " + ((SipServletRequestImpl)inviteAppData.getSipServletMessage()).is1xxResponseGenerated());
}
if(logger.isDebugEnabled()) {
logger.debug("invite message : Final response was generated ? " + ((SipServletRequestImpl)inviteAppData.getSipServletMessage()).isFinalResponseGenerated());
}
if(logger.isDebugEnabled()) {
logger.debug("replying 487 to INVITE cancelled");
}
//otherwise it means that this is for the app
try {
send487Response(inviteTransaction, inviteRequest);
} catch(IllegalStateException iae) {
logger.info("request already proxied, dropping the cancel");
return;
}
try{
callServlet(sipServletRequest);
} catch (ServletException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected servlet exception occured while routing the following CANCEL " + request, e);
} catch (IOException e) {
throw new DispatcherException(Response.SERVER_INTERNAL_ERROR, "An unexpected IO exception occured while routing the following CANCEL " + request, e);
}
}
}
}
}