@Override
  public synchronized void forwardTo(final Call call, final Map<String, String> headers)
      throws SignalException {
    if (!(call instanceof SIPCall)) {
      throw new UnsupportedOperationException("Cannot forward to non-SIPCall.");
    }
    if (_req.isInitial()) {
      throw new IllegalArgumentException("Cannot forward initial SIP request.");
    }
    final SIPCallImpl scall = (SIPCallImpl) call;
    if (!scall.isAnswered()) {
      throw new IllegalStateException("Cannot forward to no-answered call.");
    }
    this.checkState();

    _forwarded = true;
    final SipSession session = scall.getSipSession();
    final SipServletRequest req = session.createRequest(_req.getMethod());
    SIPHelper.addHeaders(req, headers);
    SIPHelper.copyContent(_req, req);
    SIPHelper.linkSIPMessage(_req, req);
    try {
      req.send();
    } catch (final IOException e) {
      throw new SignalException(e);
    }
  }
  @Override
  protected void unmute(SIPCallImpl call) throws IOException, SdpException {
    final SIPCallImpl peer = (SIPCallImpl) call.getLastPeer();

    SipServletRequest reInvite = call.getSipSession().createRequest("INVITE");
    reInvite.setAttribute(SIPCallDelegate.SIPCALL_UNMUTE_REQUEST, "true");
    reInvite.setContent(createSendrecvSDP(call, peer.getRemoteSdp()), "application/sdp");
    reInvite.send();
  }
 @Override
 protected void handleReinvite(
     final SIPCallImpl call, final SipServletRequest req, final Map<String, String> headers)
     throws Exception {
   final SIPCallImpl peer = (SIPCallImpl) call.getLastPeer();
   final SipServletRequest newReq = peer.getSipSession().createRequest(req.getMethod());
   SIPHelper.addHeaders(newReq, headers);
   SIPHelper.copyContent(req, newReq);
   SIPHelper.linkSIPMessage(req, newReq);
   newReq.send();
 }
  @Override
  protected void hold(SIPCallImpl call, boolean send)
      throws MsControlException, IOException, SdpException {
    final SIPCallImpl peer = (SIPCallImpl) call.getLastPeer();

    SipServletRequest reInvite = call.getSipSession().createRequest("INVITE");
    reInvite.setAttribute(SIPCallDelegate.SIPCALL_HOLD_REQUEST, "true");
    reInvite.setContent(createSendonlySDP(call, peer.getRemoteSdp()), "application/sdp");
    reInvite.send();

    peer.hold();
  }
  @Override
  protected void handleReinviteResponse(
      final SIPCallImpl call, final SipServletResponse res, final Map<String, String> headers) {

    if (res.getRequest().getAttribute(SIPCallDelegate.SIPCALL_HOLD_REQUEST) != null
        || res.getRequest().getAttribute(SIPCallDelegate.SIPCALL_UNHOLD_REQUEST) != null
        || res.getRequest().getAttribute(SIPCallDelegate.SIPCALL_DEAF_REQUEST) != null) {
      try {
        res.createAck().send();
        if (call.getHoldState() == HoldState.Holding) {
          call.setHoldState(HoldState.Held);
        } else if (call.getHoldState() == HoldState.UnHolding) {
          call.setHoldState(HoldState.None);
        } else if (call.getDeafState() == HoldState.Deafing) {
          call.setDeafState(HoldState.Deafed);
        } else if (call.getDeafState() == HoldState.Undeafing) {
          call.setDeafState(HoldState.None);
        }
      } catch (IOException e) {
        LOG.error("IOException when sending back ACK.", e);
        call.setHoldState(HoldState.None);
        call.setDeafState(HoldState.None);
        call.fail(e);
      } finally {
        call.notify();
      }
    } else if (res.getRequest().getAttribute(SIPCallDelegate.SIPCALL_MUTE_REQUEST) != null
        || res.getRequest().getAttribute(SIPCallDelegate.SIPCALL_UNMUTE_REQUEST) != null) {
      // send ACK.
      try {
        res.createAck().send();

        // send the received SDP to peer.
        final SIPCallImpl peer = (SIPCallImpl) call.getLastPeer();
        synchronized (peer) {
          if (call.getMuteState() == HoldState.Muting) {
            peer.setDeafState(HoldState.Deafing);
          } else {
            peer.setDeafState(HoldState.Undeafing);
          }

          SipServletRequest reInvite = peer.getSipSession().createRequest("INVITE");
          reInvite.setAttribute(SIPCallDelegate.SIPCALL_DEAF_REQUEST, "true");
          reInvite.setContent(res.getRawContent(), "application/sdp");
          reInvite.send();

          while (peer.getDeafState() != HoldState.Deafed && peer.getDeafState() != HoldState.None) {
            try {
              peer.wait();
            } catch (InterruptedException e) {
              LOG.warn(
                  "InterruptedException when wait make peer deaf, the peer's DeafState "
                      + peer.getDeafState());
            }
          }

          // set call deaf state
          if (call.getMuteState() == HoldState.Muting) {
            peer.setDeafState(HoldState.Deafed);
            call.setMuteState(HoldState.Muted);
          } else if (call.getMuteState() == HoldState.UnMuting) {
            peer.setDeafState(HoldState.None);
            call.setMuteState(HoldState.None);
          }
        }
      } catch (IOException e1) {
        LOG.error("IOException", e1);
        call.setMuteState(HoldState.None);
        call.fail(e1);
      } finally {
        call.notify();
      }
    } else {
      try {
        final SipServletRequest req = res.getRequest();
        final SipServletRequest newReq = (SipServletRequest) SIPHelper.getLinkSIPMessage(req);
        if (newReq != null) {
          SIPHelper.unlinkSIPMessage(req);
          final SipServletResponse newRes =
              newReq.createResponse(res.getStatus(), res.getReasonPhrase());
          SIPHelper.addHeaders(newRes, headers);
          SIPHelper.copyContent(res, newRes);
          if (SIPHelper.isReinvite(newRes)) {
            newRes.getSession().setAttribute(REINVITE_PEER_RES, res);
          }
          newRes.send();
        }
      } catch (final Exception e) {
        LOG.warn("", e);
        return;
      }
    }
  }