Package org.mobicents.slee.service.callcontrol

Source Code of org.mobicents.slee.service.callcontrol.CallControlSbb

/*
* Mobicents: The Open Source SLEE Platform     
*
* Copyright 2003-2005, CocoonHive, LLC.,
* and individual contributors as indicated
* by the @authors tag. See the copyright.txt
* in the distribution for a full listing of  
* individual contributors.
*
* 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.slee.service.callcontrol;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sip.ClientTransaction;
import javax.sip.Dialog;
import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.TransactionUnavailableException;
import javax.sip.header.CallIdHeader;
import javax.sip.header.ContactHeader;
import javax.sip.header.Header;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import javax.slee.ActivityContextInterface;
import javax.slee.FactoryException;
import javax.slee.InitialEventSelector;
import javax.slee.RolledBackContext;
import javax.slee.SbbContext;
import javax.slee.SbbLocalObject;
import javax.slee.UnrecognizedActivityException;

import org.apache.log4j.Logger;
import org.mobicents.mscontrol.MsConnection;
import org.mobicents.mscontrol.MsConnectionEvent;
import org.mobicents.mscontrol.MsLink;
import org.mobicents.mscontrol.MsProvider;
import org.mobicents.mscontrol.MsSession;
import org.mobicents.slee.resource.media.ratype.MediaRaActivityContextInterfaceFactory;
import org.mobicents.slee.resource.sip.SipActivityContextInterfaceFactory;
import org.mobicents.slee.resource.sip.SipFactoryProvider;
import org.mobicents.slee.service.common.SimpleCallFlowRequestState;
import org.mobicents.slee.service.common.SimpleCallFlowResponseState;
import org.mobicents.slee.service.common.SimpleCallFlowState;
import org.mobicents.slee.service.events.CustomEvent;
import org.mobicents.slee.util.CacheException;
import org.mobicents.slee.util.CacheFactory;
import org.mobicents.slee.util.CacheUtility;
import org.mobicents.slee.util.Session;
import org.mobicents.slee.util.SessionAssociation;
import org.mobicents.slee.util.SipUtils;
import org.mobicents.slee.util.SipUtilsFactorySingleton;
import org.mobicents.slee.util.StateCallback;

public abstract class CallControlSbb implements javax.slee.Sbb {
  private static Logger log = Logger.getLogger(CallControlSbb.class);

  public final static String ENDPOINT_NAME = "media/trunk/PacketRelay/$";

  public final static String ANNOUNCEMENT_ENDPOINT = "media/trunk/Announcement/$";

  private SbbContext sbbContext; // This SBB's SbbContext

  private SipProvider sipProvider;

  private SipActivityContextInterfaceFactory activityContextInterfaceFactory;

  private MsProvider msProvider;

  private MediaRaActivityContextInterfaceFactory mediaAcif;

  private SipUtils sipUtils;

  private CacheUtility cache;

  private String callControlSipAddress;

  private String password;

  public abstract ResponseEvent getResponseEventCmp();

  public abstract void setResponseEventCmp(ResponseEvent evt);

  public abstract void setParentCmp(SbbLocalObject sbbLocalObject);

  public abstract SbbLocalObject getParentCmp();

  public abstract void setCustomEventCmp(CustomEvent sbbLocalObject);

  public abstract CustomEvent getCustomEventCmp();

  public void setCustomEvent(CustomEvent event) {
    setCustomEventCmp(event);
  }

  public void setParent(SbbLocalObject sbbLocalObject) {
    setParentCmp(sbbLocalObject);
  }

  public ResponseEvent getResponseEvent() {
    return getResponseEventCmp();
  }

  /**
   * Generate a custom convergence name so that events with the same call
   * identifier will go to the same root SBB entity. For other methods, use
   * ActivityContext.
   */
  public InitialEventSelector callIdSelect(InitialEventSelector ies) {
    log.info("***************     callIdSelect     ***************");
    Object event = ies.getEvent();
    String callId = null;
    if (event instanceof ResponseEvent) {
      // If response event, the convergence name to callId
      Response response = ((ResponseEvent) event).getResponse();
      callId = ((CallIdHeader) response.getHeader(CallIdHeader.NAME))
          .getCallId();
    } else if (event instanceof RequestEvent) {
      // If request event, the convergence name to callId
      Request request = ((RequestEvent) event).getRequest();
      callId = ((CallIdHeader) request.getHeader(CallIdHeader.NAME))
          .getCallId();
    } else {
      // If something else, use activity context.
      ies.setActivityContextSelected(true);
      return ies;
    }
    // Set the convergence name
    if (log.isDebugEnabled()) {
      log.debug("Setting convergence name to: " + callId);
    }
    ies.setCustomName(callId);
    return ies;
  }

  private void executeRequestState(RequestEvent event) {
    String callId = ((CallIdHeader) event.getRequest().getHeader(
        CallIdHeader.NAME)).getCallId();
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    SimpleCallFlowState simpleCallFlowState = getState(sa.getState());
    simpleCallFlowState.execute(event);
  }

  private void executeResponseState(ResponseEvent event) {
    String callId = ((CallIdHeader) event.getResponse().getHeader(
        CallIdHeader.NAME)).getCallId();
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    SimpleCallFlowState simpleCallFlowState = getState(sa.getState());
    simpleCallFlowState.execute(event);
  }

  // public void onAckEvent(RequestEvent event, ActivityContextInterface aci)
  // {
  // if (log.isDebugEnabled()) {
  // log.debug("Received: ACK");
  // }
  // executeRequestState(event);
  // }

  public void onByeEvent(RequestEvent event, ActivityContextInterface aci) {

    log.info("************Received BYEEEE**************");

    if (log.isDebugEnabled()) {
      log.debug("Received BYE");
    }

    releaseMediaConnectionAndDialog();

    try {
      sipUtils.sendStatefulOk(event);
    } catch (ParseException e) {
      e.printStackTrace();
    } catch (SipException e) {
      e.printStackTrace();
    } catch (InvalidArgumentException invalidArgEx) {
      invalidArgEx.printStackTrace();
    }

    // executeRequestState(event);
  }

  // public void onCancelEvent(RequestEvent event, ActivityContextInterface
  // aci) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received CANCEL");
  // }
  // executeRequestState(event);
  // }

  // public void onServerErrorRespEvent(ResponseEvent event,
  // ActivityContextInterface aci) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received server error response : "
  // + event.getResponse().getStatusCode());
  // }
  // executeResponseState(event);
  // }
  //
  public void onClientErrorRespEvent(ResponseEvent event,
      ActivityContextInterface aci) {
    if (log.isDebugEnabled()) {
      log.debug("Received client error event : "
          + event.getResponse().getStatusCode());
    }
    executeResponseState(event);
  }

  //
  // public void onGlobalFailureRespEvent(ResponseEvent event,
  // ActivityContextInterface aci) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received global failure event : "
  // + event.getResponse().getStatusCode());
  // }
  // executeResponseState(event);
  // }

  public void onSuccessRespEvent(ResponseEvent event,
      ActivityContextInterface aci) {
    if (log.isDebugEnabled()) {
      log.debug("Received success response event "
          + event.getResponse().getStatusCode());
    }
    executeResponseState(event);
  }

  // public void onInfoRespEvent(ResponseEvent event,
  // ActivityContextInterface aci) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received informational response event : "
  // + event.getResponse().getStatusCode());
  // }
  // executeResponseState(event);
  // }

  /*
   * Timeouts
   */
  // public void onTransactionTimeoutEvent(TimeoutEvent event,
  // ActivityContextInterface ac) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received timeout event");
  // }
  // }
  //
  // public void onRetransmitTimeoutEvent(TimeoutEvent event,
  // ActivityContextInterface ac) {
  // if (log.isDebugEnabled()) {
  // log.debug("Received retransmit timeout event");
  // }
  // }
  public void onConnectionCreated(MsConnectionEvent evt,
      ActivityContextInterface aci) {
    log.info("--------------onConnectionCreated--------------");
    MsConnection connection = evt.getConnection();

    log.info("Created RTP connection [" + connection.getEndpoint() + "]");

    try {
      Dialog dialog = sipUtils.getDialog(getResponseEventCmp());
      Request ackRequest = sipUtils.buildAck(dialog, connection
          .getLocalDescriptor());
      dialog.sendAck(ackRequest);
    } catch (SipException e) {
      e.printStackTrace();
    }
    MsSession session = connection.getSession();
    MsLink link = session.createLink(MsLink.MODE_FULL_DUPLEX);

    ActivityContextInterface linkActivity = null;
    try {
      linkActivity = mediaAcif.getActivityContextInterface(link);
    } catch (UnrecognizedActivityException ex) {
      ex.printStackTrace();
    }

    linkActivity.attach(getParentCmp());

    link.join(connection.getEndpoint(), ANNOUNCEMENT_ENDPOINT);
  }

  private void releaseMediaConnectionAndDialog() {
    ActivityContextInterface[] activities = sbbContext.getActivities();
    SbbLocalObject sbbLocalObject = getSbbContext().getSbbLocalObject();
    MsConnection msConnection = null;
    for (ActivityContextInterface attachedAci : activities) {
      if (attachedAci.getActivity() instanceof Dialog) {
        attachedAci.detach(sbbLocalObject);
        attachedAci.detach(this.getParentCmp());
      }
      if (attachedAci.getActivity() instanceof MsConnection) {
        attachedAci.detach(sbbLocalObject);
        msConnection = (MsConnection) attachedAci.getActivity();
        attachedAci.detach(this.getParentCmp());
      }
    }
    if (msConnection != null) {
      msConnection.release();
    }
  }

  public void sendBye() {

    releaseMediaConnectionAndDialog();

    try {
      Dialog dialog = sipUtils.getDialog(getResponseEventCmp());
      sendRequest(dialog, Request.BYE);
    } catch (SipException e) {
      log.error("Error sending BYE", e);
    }
  }

  // TODO: Perform further operations if required in these methods.
  public void setSbbContext(SbbContext context) {
    this.sbbContext = context;

    try {
      // Create the cache used for the session association
      cache = CacheFactory.getInstance().getCache();

      Context myEnv = (Context) new InitialContext()
          .lookup("java:comp/env");
      // Getting JAIN SIP Resource Adaptor interfaces
      SipFactoryProvider factoryProvider = (SipFactoryProvider) myEnv
          .lookup("slee/resources/jainsip/1.2/provider");

      sipProvider = factoryProvider.getSipProvider();

      activityContextInterfaceFactory = (SipActivityContextInterfaceFactory) myEnv
          .lookup("slee/resources/jainsip/1.2/acifactory");

      // Check that callControlSipAddress and password are present
      callControlSipAddress = (String) myEnv
          .lookup("callControlSipAddress");
      password = (String) myEnv.lookup("password");
      String passwordDisplay = password == null ? "null" : "*******";
      if (log.isDebugEnabled()) {
        log
            .debug("Checking that CallControlSbb callControlSipAddress and password are present : callControlSipAddress = "
                + callControlSipAddress
                + " password = "
                + passwordDisplay);
      }

      sipUtils = SipUtilsFactorySingleton.getInstance().getSipUtils();

      // initilize Media API
      msProvider = (MsProvider) myEnv
          .lookup("slee/resources/media/1.0/provider");
      mediaAcif = (MediaRaActivityContextInterfaceFactory) myEnv
          .lookup("slee/resources/media/1.0/acifactory");

    } catch (NamingException ne) {
      ne.printStackTrace();
    } catch (CacheException e) {
      e.printStackTrace();
    }
  }

  public void unsetSbbContext() {
    this.sbbContext = null;
  }

  // TODO: Implement the lifecycle methods if required
  public void sbbCreate() throws javax.slee.CreateException {
  }

  public void sbbPostCreate() throws javax.slee.CreateException {
  }

  public void sbbActivate() {
  }

  public void sbbPassivate() {
  }

  public void sbbRemove() {
  }

  public void sbbLoad() {
  }

  public void sbbStore() {
  }

  public void sbbExceptionThrown(Exception exception, Object event,
      ActivityContextInterface activity) {
  }

  public void sbbRolledBack(RolledBackContext context) {
  }

  /**
   * Convenience method to retrieve the SbbContext object stored in
   * setSbbContext. TODO: If your SBB doesn't require the SbbContext object
   * you may remove this method, the sbbContext variable and the variable
   * assignment in setSbbContext().
   *
   * @return this SBB's SbbContext object
   */

  protected SbbContext getSbbContext() {
    return sbbContext;
  }

  /**
   * Store the client transaction in the cache since we may need to send a
   * cancel request associated with this ClientTransaction later.
   *
   * @param The
   *            client transaction to store as "to be cancelled".
   */
  private void setToBeCancelledClientTransaction(ClientTransaction ct) {
    String callId = ((CallIdHeader) ct.getRequest().getHeader(
        CallIdHeader.NAME)).getCallId();
    SessionAssociation sa = (SessionAssociation) cache.get(callId);

    if (sa != null) {
      Session session = sa.getSession(callId);
      session.setToBeCancelledClientTransaction(ct);
    }
  }

  private SimpleCallFlowState getState(String classNameForState) {
    SimpleCallFlowState simpleCallFlowState = null;
    try {
      Class innerCls = Class.forName(classNameForState);
      Constructor c = innerCls.getDeclaredConstructors()[0];
      simpleCallFlowState = (SimpleCallFlowState) c
          .newInstance(new Object[] { this });
    } catch (InstantiationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (SecurityException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IllegalArgumentException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return simpleCallFlowState;
  }

  public abstract class StateSupport implements SimpleCallFlowState {
    private String statusMessage;

    public StateSupport(String statusMessage) {
      this.statusMessage = statusMessage;
    }

    public String getStatusMessage() {
      return statusMessage;
    }
  }

  public abstract class BeforeCalleeConfirmedState extends
      SimpleCallFlowResponseState {
    // TODO Handle exception
    public void handleTrying(String calleeCallId, ResponseEvent event) {

      setState(new CalleeTryingState(), calleeCallId);
    }

    // TODO Handle exception
    public void handleRinging(String calleeCallId, ResponseEvent event) {

      setState(new CalleeRingingState(), calleeCallId);
    }

    public void handleOK(String calleeCallId, ResponseEvent event) {

      log.debug("BeforeCalleeConfirmedState.handleOK");

      setResponseEventCmp(event);
      Dialog currentDialog = null;

      // Check that the dialog of the event is the same as in the session
      // If not this is the result of a fork, we don't handle this
      try {
        Dialog eventDialog = sipUtils.getDialog(event);
        currentDialog = getDialog(eventDialog.getCallId().getCallId());
        if (!eventDialog.equals(currentDialog)) {
          log.warn("Received 200 response from forked dialog");
          return; // We don't currently handle this. Should send ACK
          // and BYE
        }
      } catch (SipException e) {
        log.error("SipException while trying to retreive Dialog", e);
      }

      // Let us attach sbbLocalObject to Dialog to receive Bye latter
      try {

        ActivityContextInterface daci = activityContextInterfaceFactory
            .getActivityContextInterface(currentDialog);
        daci.attach(sbbContext.getSbbLocalObject());
      } catch (Exception e) {
        e.printStackTrace();
      }

      String sdp = new String(event.getResponse().getRawContent());

      log.debug("BeforeCalleeConfirmedState.handleOK sdpOffer = " + sdp);

      MsSession session = msProvider.createSession();
      MsConnection msConnection = session
          .createNetworkConnection(ENDPOINT_NAME);

      try {
        ActivityContextInterface aci = mediaAcif
            .getActivityContextInterface(msConnection);
        aci.attach(sbbContext.getSbbLocalObject());
        aci.attach(getParentCmp());
      } catch (Exception e) {
        e.printStackTrace();
      }

      log.info("Creating RTP connection [" + ENDPOINT_NAME + "]");

      msConnection.modify("$", sdp);

      setState(new SessionEstablishedState(), calleeCallId);
    }

    public void handleDecline(String calleeCallId, ResponseEvent event) {

      // Not sending ACK. Is it ok?
      // sendCallerAck(event);

      // Dialog dialog = null;
      // // TODO Handle exception
      // try {
      // dialog = sipUtils.getDialog(event);
      // } catch (SipException e) {
      // log.error("Error getting dialog in handleDecline", e);
      // }
      // Request ackRequest;
      // try {
      // ackRequest = dialog.createRequest(Request.ACK);
      // dialog.sendAck(ackRequest);
      // } catch (SipException e) {
      // // TODO Auto-generated catch block
      // e.printStackTrace();
      // }
      setState(new TerminationState(), calleeCallId);

    }

    public void handleAuthentication(String calleeCallId,
        ResponseEvent event) {     
      sendRequestWithAuthorizationHeader(event);
      setState(new InitialState(), calleeCallId);
    }

    public abstract void execute(ResponseEvent event);

  }

  public class InitialState extends BeforeCalleeConfirmedState {

    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      int status = response.getStatusCode();
      final String calleeCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();
      if (status == Response.TRYING) {
        log
            .debug("org.mobicents.slee.service.callcontrol.CallControlSbb$InitialState - execute - status == Response.TRYING - calleeCallId = "
                + calleeCallId);
        // status == 100
        handleTrying(calleeCallId, event);
      } else if (status >= Response.RINGING && status < Response.OK) {
        // 180 <= status < 200
        log
            .debug("org.mobicents.slee.service.callcontrol.CallControlSbb$InitialState - execute - status >= Response.RINGING && status < Response.OK - calleeCallId = "
                + calleeCallId);
        handleRinging(calleeCallId, event);
      } else if (status >= Response.OK && status <= Response.ACCEPTED) {
        // 200 <= status <= 202
        log
            .debug("org.mobicents.slee.service.callcontrol.CallControlSbb$InitialState - execute - status >= Response.OK && status <= Response.ACCEPTED - calleeCallId = "
                + calleeCallId);
        handleOK(calleeCallId, event);
      } else if (status == Response.DECLINE) {
        log
            .debug("org.mobicents.slee.service.callcontrol.CallControlSbb$InitialState - execute - status == Response.DECLINE - calleeCallId = "
                + calleeCallId);
        handleDecline(calleeCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        log
            .debug("org.mobicents.slee.service.callcontrol.CallControlSbb$InitialState - execute - status == Response.UNAUTHORIZED || status == Response.PROXY_AUTHENTICATION_REQUIRED - calleeCallId = "
                + calleeCallId);
        handleAuthentication(calleeCallId, event);
      }
    }
  }

  public class CalleeTryingState extends BeforeCalleeConfirmedState {

    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      int status = response.getStatusCode();
      final String calleeCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status >= Response.RINGING && status < Response.OK) {
        log
            .debug("CallControlSbb$CalleeTryingState - execute - status >= Response.RINGING && status < Response.OK - calleeCallId = "
                + calleeCallId);
        // 180 <= status < 200
        handleRinging(calleeCallId, event);
      } else if (status >= Response.OK && status <= Response.ACCEPTED) {
        // 200 <= status <= 202
        log
            .debug("CallControlSbb$CalleeTryingState - execute - status >= Response.OK && status <= Response.ACCEPTED - calleeCallId = "
                + calleeCallId);
        handleOK(calleeCallId, event);
      } else if (status == Response.DECLINE) {
        log
            .debug("CallControlSbb$CalleeTryingState - execute - status == Response.DECLINE - calleeCallId = "
                + calleeCallId);
        handleDecline(calleeCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        log
            .debug("CallControlSbb$CalleeTryingState - execute - status == Response.UNAUTHORIZED || status == Response.PROXY_AUTHENTICATION_REQUIRED - calleeCallId = "
                + calleeCallId);
        handleAuthentication(calleeCallId, event);
      }
    }

  }

  public class CalleeRingingState extends BeforeCalleeConfirmedState {

    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      int status = response.getStatusCode();
      final String calleeCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status >= Response.OK && status <= Response.ACCEPTED) {
        handleOK(calleeCallId, event);
      } else if (status == Response.DECLINE) {
        handleDecline(calleeCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        handleAuthentication(calleeCallId, event);
      }
    }

  }

  public abstract class AfterCalleeConfirmedAndBeforeCallerConfirmedState
      implements SimpleCallFlowState {

    public void handleTrying(String callerCallId, ResponseEvent event) {
      // TODO Handle exception

      setState(new CallerTryingState(), callerCallId);
    }

    // TODO Handle exception
    public void handleRinging(String callerCallId, ResponseEvent event) {
      setState(new CallerRingingState(), callerCallId);
    }

    // TODO Handle exception
    public void handleOK(String callerCallId, ResponseEvent event) {

      // Check that the dialog of the event is the same as in the session
      // If not this is the result of a fork, we don't handle this
      try {
        Dialog eventDialog = sipUtils.getDialog(event);
        Dialog currentDialog = getDialog(eventDialog.getCallId()
            .getCallId());
        if (!eventDialog.equals(currentDialog)) {
          log.warn("Received 200 response from forked dialog");
          return; // We don't currently handle this. Should send ACK
          // and BYE
        }
      } catch (SipException e) {
        // TODO Handle this
      }

      sendCalleeAck(event);
      sendCallerAck(event);
      setState(new SessionEstablishedState(), callerCallId);

    }

    /**
     * Used to handle responses in the 400 series.
     *
     * @param callerCallId
     */
    public void handleError(String callerCallId) {
      Dialog dialog = getPeerDialog(callerCallId);
      try {
        sendRequest(dialog, Request.BYE);
      } catch (SipException e) {
        log.error("Error sending BYE", e);
      }
      setState(new UATerminationState(), callerCallId);
    }

    public void handleDecline(String callerCallId) {
      Dialog dialog = getPeerDialog(callerCallId);
      try {
        sendRequest(dialog, Request.BYE);
      } catch (SipException e) {
        log.error("Error sending BYE", e);
      }
      setState(new UATerminationState(), callerCallId);
    }

    public void handleAuthentication(String callerCallId,
        ResponseEvent event) {
      sendRequestWithAuthorizationHeader(event);
      setState(new CallerInvitedState(), callerCallId);
    }

    /**
     * Handles a BYE
     *
     * @param calleeCallId
     * @param request
     */
    public void handleBye(String calleeCallId, Request request) {
      // Send OK to callee
      try {
        sipUtils.sendOk(request);
      } catch (ParseException e) {
        e.printStackTrace();
      } catch (SipException e) {
        e.printStackTrace();
      }
      // Send Cancel to caller
      sendRequestCancel(getPeerDialog(calleeCallId));
      setState(new UATerminationState(), calleeCallId);
    }

    public abstract void execute(RequestEvent event);

    public abstract void execute(ResponseEvent event);

  }

  public class CallerInvitedState extends
      AfterCalleeConfirmedAndBeforeCallerConfirmedState {

    public void execute(ResponseEvent event) {

      final Response response = event.getResponse();
      final int status = response.getStatusCode();
      final String callerCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status == Response.TRYING) { // status == 100
        handleTrying(callerCallId, event);
      } else if (status >= Response.RINGING && status < Response.OK) {
        // 180 <= status < 200
        handleRinging(callerCallId, event);
      } else if (status >= Response.OK && status <= Response.ACCEPTED) {
        // 200 <= status <= 202
        handleOK(callerCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        handleAuthentication(callerCallId, event);
      } else if (status >= Response.BAD_REQUEST
          && status < Response.SERVER_INTERNAL_ERROR) {
        // 400 <= status < 500
        handleError(callerCallId);
      } else if (status == Response.DECLINE) {
        // Status = 603
        handleDecline(callerCallId);
      }
    }

    public void execute(RequestEvent event) {
      Request request = event.getRequest();
      final String calleeCallId = ((CallIdHeader) request
          .getHeader(CallIdHeader.NAME)).getCallId();
      String method = request.getMethod();

      if (Request.BYE.equals(method)) {
        handleBye(calleeCallId, request);
      }
    }
  }

  public class CallerTryingState extends
      AfterCalleeConfirmedAndBeforeCallerConfirmedState {

    public void execute(ResponseEvent event) {
      final Response response = event.getResponse();
      final int status = response.getStatusCode();
      final String callerCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status >= Response.RINGING && status < Response.OK) {
        // 180 <= status < 200
        handleRinging(callerCallId, event);
      } else if (status >= Response.OK && status <= Response.ACCEPTED) {
        // 200 <= status <= 202
        handleOK(callerCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        handleAuthentication(callerCallId, event);
      } else if (status >= Response.BAD_REQUEST
          && status < Response.SERVER_INTERNAL_ERROR) {
        // 400 <= status < 500
        handleError(callerCallId);
      } else if (status == Response.DECLINE) {
        // Status = 603
        handleDecline(callerCallId);
      }
    }

    public void execute(RequestEvent event) {
      Request request = event.getRequest();
      final String calleeCallId = ((CallIdHeader) request
          .getHeader(CallIdHeader.NAME)).getCallId();
      String method = request.getMethod();

      if (Request.BYE.equals(method)) {
        handleBye(calleeCallId, request);
      }
    }

  }

  public class CallerRingingState extends
      AfterCalleeConfirmedAndBeforeCallerConfirmedState {

    public void execute(ResponseEvent event) {
      final Response response = event.getResponse();
      final int status = response.getStatusCode();
      final String callerCallId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status >= Response.OK && status <= Response.ACCEPTED) {
        // 200 <= status <= 202
        handleOK(callerCallId, event);
      } else if (status == Response.UNAUTHORIZED
          || status == Response.PROXY_AUTHENTICATION_REQUIRED) {
        handleAuthentication(callerCallId, event);
      } else if (status >= Response.BAD_REQUEST
          && status < Response.SERVER_INTERNAL_ERROR) {
        // 400 <= status < 500
        handleError(callerCallId);
      } else if (status == Response.DECLINE) {
        // Status = 603
        handleDecline(callerCallId);
      }
    }

    public void execute(RequestEvent event) {
      Request request = event.getRequest();
      final String calleeCallId = ((CallIdHeader) request
          .getHeader(CallIdHeader.NAME)).getCallId();
      String method = request.getMethod();

      if (Request.BYE.equals(method)) {
        handleBye(calleeCallId, request);
      }
    }
  }

  public class SessionEstablishedState extends SimpleCallFlowRequestState {

    public void execute(RequestEvent event) {
      Request request = event.getRequest();
      final String method = request.getMethod();

      if (method.equals(Request.BYE)) {
        final String callId = ((CallIdHeader) request
            .getHeader(CallIdHeader.NAME)).getCallId();

        Dialog dialog = getPeerDialog(callId);
        try {
          sipUtils.sendOk(request);
          sendRequest(dialog, Request.BYE);
        } catch (ParseException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
        } catch (SipException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
        }

        setState(new UATerminationState(), callId);
      }
    }
  }

  public class UATerminationState extends SimpleCallFlowResponseState {
    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      int status = response.getStatusCode();
      final String callId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      if (status == Response.OK) { // status == 200
        setState(new TerminationState(), callId);

      }
    }
  }

  /**
   * We enter this state when an external cancellation has been triggered
   * (e.g. by invocation of cancel(String guid) method ) and CANCEL has been
   * sent to calleee.
   *
   */
  public class ExternalCancellationState extends SimpleCallFlowResponseState {
    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      // We expect a 200 OK
      // However, we should send switch state to TerminationState whatever
      // the response is
      // so we simply ignore the response for now.
      final String callId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();
      setState(new TerminationState(), callId);
    }
  }

  /**
   * We enter this state when the termination of both sessions has been
   * triggered (e.g. by invocation of terminate(String guid) method ) and BYE
   * has been sent to callee.
   *
   * @author niklas
   *
   */
  public class ExternalTerminationCalleeState extends
      SimpleCallFlowResponseState {
    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      // We expect a 200 OK and send a bye to Caller
      // However, we should send BYE to the Caller whatever the response
      // is
      // so we simply ignore the response for now.
      final String callId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();

      Dialog dialog = getPeerDialog(callId);
      // TODO Handle exception better (exception indicated in termination
      // state)
      try {
        sendRequest(dialog, Request.BYE);
        setState(new ExternalTerminationCallerState(), callId);
      } catch (SipException e) {
        log
            .error("Exception while sending BYE in execute for callId : "
                + dialog.getCallId().getCallId());
        setState(new TerminationState(), callId);
      }

    }
  }

  /**
   * We enter this state when the termination of both sessions has been
   * triggered (e.g. by invocation of terminateDialogs(String guid) ), BYE has
   * been sent to both parties and we wait for a response for the Caller. <br>
   * When this arrives the session has been successfully teared down.
   *
   * @author niklas
   *
   */
  public class ExternalTerminationCallerState extends
      SimpleCallFlowResponseState {
    public void execute(ResponseEvent event) {
      Response response = event.getResponse();
      // We expect a 200 OK but move to TerminationState no matter
      // what the response status is.

      // TODO Introduce a termination cause property on the
      // TerminationEvent to communicate this information
      final String callId = ((CallIdHeader) response
          .getHeader(CallIdHeader.NAME)).getCallId();
      setState(new TerminationState(), callId);

    }
  }

  public class TerminationState extends SimpleCallFlowResponseState {

    public void execute(ResponseEvent event) {
      // FINAL state, do nothing!
    }

  }

  /**
   * Associates a state object to the association of the two sip dialogs
   * defining the unit of call control.
   *
   * @param state
   * @param callId
   */
  private void setState(SimpleCallFlowState state, String callId) {
    if (log.isDebugEnabled()) {
      log.debug("Setting state to " + state + " for callId " + callId);
      ;
    }
    String stateName = state.getClass().getName();
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    // This state is used to manage the state machine transitions in the Sbb
    // implementation
    sa.setState(stateName);
    // Also communicate the state to external observers
    StateCallback callback = sa.getStateCallback();
    if (callback != null) { // The callback property is optional and can be
      // null
      callback.setSessionState(stateName);
    }

  }

  /**
   * Utility method that associates a Dialog to the Session it belongs to via
   * its callID. <br>
   * <br>
   * Note: This is a hack that needs to be fixed. There can be multiple
   * dialogs that arise as a consequence of an invite being sent. This must be
   * dealt with.
   *
   *
   * @param dialog
   * @param callId
   */
  // TODO Fix the hack referred to above
  private void setDialog(Dialog dialog, String callId) {
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    Session session = sa.getSession(callId);
    if (log.isDebugEnabled()) {
      log.debug("Setting dialog in session for callId : " + callId);
    }
    session.setDialog(dialog);
  }

  public void sendCallerAck(ResponseEvent event) {
    try {
      Dialog dialog = sipUtils.getDialog(event);
      Request ackRequest = sipUtils.buildAck(dialog, null);
      dialog.sendAck(ackRequest);
    } catch (SipException e) {
      e.printStackTrace();
    }
  }

  /**
   * Accepts a response event and sends an ACK (containing the sdp from this
   * event) to the callee.
   *
   * @param event
   */
  private void sendCalleeAck(ResponseEvent event) {

    log.debug("Sending Calee ACK event ResposneEvent = " + event);
    try {
      ClientTransaction ct = event.getClientTransaction();
      final String callerCallId = ((CallIdHeader) ct.getRequest()
          .getHeader(CallIdHeader.NAME)).getCallId();

      Dialog calleeDialog = getPeerDialog(callerCallId);
      Object content = event.getResponse().getContent();

      log.debug("Building ACK content = " + content + " Dialog = "
          + calleeDialog);
      Request ackRequest = sipUtils.buildAck(calleeDialog, content);
      calleeDialog.sendAck(ackRequest);

    } catch (SipException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  private Dialog getPeerDialog(String callId) {
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    Session peerSession = sa.getPeerSession(callId);
    return peerSession.getDialog();
  }

  private Dialog getDialog(String callId) {
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    Session session = sa.getSession(callId);
    return session.getDialog();
  }

  /**
   * The cancel request uses the same sequence number as the invite it is ment
   * to cancel and thus needs to be treated specially. I.e. retrive the
   * sequence number from this invite and use this in the new cancel request.
   *
   * @param dialog
   *            The dialog of the UA where the cancel is to be sent.
   */
  private void sendRequestCancel(Dialog dialog) {
    // Send to callee
    try {
      // Retrieve the client transacation to cancel
      Session session = getSession(dialog);
      ClientTransaction ct = session.getToBeCancelledClientTransaction();
      sipUtils.sendCancel(ct);
    } catch (SipException e) {
      e.printStackTrace();
    }
  }

  /**
   * Send a request to a UA.
   *
   * @param dialog
   *            The dialog of the UA where the request is to be sent.
   * @param requestType
   *            The request type to send to the UA.
   */
  private void sendRequest(Dialog dialog, String requestType)
      throws SipException {
    Request request;
    request = dialog.createRequest(requestType);

    /*
     * If the request is of type BYE, modify the via and contact header to
     * match the expected behavior. I.e. due to a bug in jain sip, the
     * contact header is incorrectly set to the address of the receiver.
     * This header should instead point to the address running this
     * application. The bug has been reported!
     */
    if (Request.BYE.equals(requestType)) {
      // TODO Do we have to change the VIA header?
      request.removeHeader(ViaHeader.NAME);
      request.removeHeader(ContactHeader.NAME);

      try {
        request.addHeader(sipUtils.createLocalViaHeader());
        request.addHeader(sipUtils.createLocalContactHeader());
      } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (InvalidArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    }
    // Get a client transaction and corresponding activity context interface
    // Attach ourselves to receive responses and finally send the request
    try {
      ClientTransaction ct = sipProvider.getNewClientTransaction(request);
      ActivityContextInterface acIntf = activityContextInterfaceFactory
          .getActivityContextInterface(ct);
      SbbLocalObject mySelf = sbbContext.getSbbLocalObject();
      acIntf.attach(mySelf);
      dialog.sendRequest(ct);
    } catch (Exception e) { // This catches no less than 10 distinct
      // exception types...
      log.error("Exception in sendrequest", e);
      throw new SipException(
          "Exception rethrown as SipException in sendRequest", e);
    }
  }

  /**
   * Send the request associated with the event again but this time include an
   * authorization header based on the Digest Scheme presented in RFC 2069.
   *
   * @param event
   * @param password
   */

  private void sendRequestWithAuthorizationHeader(ResponseEvent event) {
   
    ClientTransaction ct = null;
    SessionAssociation sa = null;
    try {
      Request request = sipUtils.buildRequestWithAuthorizationHeader(
          event, getPassword());

      log
          .debug("sendRequestWithAuthorizationHeader. request with Auth = "
              + request);

      ct = sipProvider.getNewClientTransaction(request);
    } catch (TransactionUnavailableException e) {
      e.printStackTrace();
    }

    Dialog dialog = ct.getDialog();
    SbbLocalObject sbbLocalObject = this.getSbbContext().getSbbLocalObject();
    // TODO Handle exception
    if (dialog == null) {
      // Automatic dialog support is off
      try {
        dialog = sipProvider.getNewDialog(ct);
        if (log.isDebugEnabled()) {
          log
              .debug("Obtained dialog with getNewDialog in sendRequestWithAuthorizationHeader");
        }
      } catch (SipException e) {
        log
            .error(
                "Error getting dialog in sendRequestWithAuthorizationHeader",
                e);
      }

      // Let us remove mapping between SessionAsscoiation and old Call-ID
      ClientTransaction ctOld = event.getClientTransaction();
      Header h = ctOld.getRequest().getHeader(CallIdHeader.NAME);
      String oldCallId = ((CallIdHeader) h).getCallId();

      sa = (SessionAssociation) cache.get(oldCallId);

      Header hNew = ct.getRequest().getHeader(CallIdHeader.NAME);
      String calleeCallIdNew = ((CallIdHeader) hNew).getCallId();

      Session oldCalleeSession = sa.getSession(oldCallId);
      oldCalleeSession.setCallId(calleeCallIdNew);

      try {
        ActivityContextInterface sipACI = activityContextInterfaceFactory
            .getActivityContextInterface(dialog);
        sipACI.attach(sbbLocalObject);
        sipACI.attach(this.getParentCmp());
      } catch (UnrecognizedActivityException aci) {
        aci.printStackTrace();
      }

      cache.put(calleeCallIdNew, sa);

    }

    final String callId = dialog.getCallId().getCallId();

    if (log.isDebugEnabled()) {
      log
          .debug("Obtained dialog from ClientTransaction in sendRequestWithAuthorizationHeader "
              + " : dialog callId = " + callId);
    }
    // Store the client transaction in the cache
    // since we may need to send a cancel request associated with this
    // ClientTransaction later.
    setToBeCancelledClientTransaction(ct);

    // Store the dialog in the cache with the associated call id of the new
    // transaction.
    setDialog(dialog, callId);

    // Since we are about the send the request in a new client transaction,
    // we need to
    // attach it to the new activity context in order to receive the
    // following events.
    // We start this by getting the activity context
    ActivityContextInterface ac = null;
    try {
      ac = activityContextInterfaceFactory
          .getActivityContextInterface(ct);
    } catch (FactoryException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (NullPointerException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (UnrecognizedActivityException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    // Attach our local interface to the new ActivityContextInterface
    // This makes this Sbb receive responses to the request
    ac.attach(sbbLocalObject);

    // Finally, send the request!
    try {
      ct.sendRequest();
    } catch (SipException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   *
   * @param callId
   * @return
   */
  private Session getSession(Dialog dialog) {
    final String callId = dialog.getCallId().getCallId();
    SessionAssociation sa = (SessionAssociation) cache.get(callId);
    Session session = sa.getSession(callId);
    return session;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public String getCallControlSipAddress() {
    return callControlSipAddress;
  }

  public void setCallControlSipAddress(String username) {
    this.callControlSipAddress = username;
  }

}
TOP

Related Classes of org.mobicents.slee.service.callcontrol.CallControlSbb

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.