/** * Copyright 2010-2011 Voxeo Corporation Licensed under the Apache License, * Version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law * or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package com.voxeo.moho.sip; import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ExecutionException; import javax.media.mscontrol.join.Joinable.Direction; import javax.media.mscontrol.networkconnection.SdpPortManagerEvent; import javax.servlet.sip.Rel100Exception; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import org.apache.log4j.Logger; import com.voxeo.moho.Call; import com.voxeo.moho.Endpoint; import com.voxeo.moho.IncomingCall; import com.voxeo.moho.Joint; import com.voxeo.moho.MediaException; import com.voxeo.moho.SignalException; import com.voxeo.moho.common.util.Utils; import com.voxeo.moho.event.CallCompleteEvent; import com.voxeo.moho.event.Observer; import com.voxeo.moho.spi.ExecutionContext; import com.voxeo.moho.util.SDPUtils; public class SIPIncomingCall extends SIPCallImpl implements IncomingCall { private static final Logger LOG = Logger.getLogger(SIPIncomingCall.class); protected Exception acceptEarlyMediaException; protected SIPCall.State oldStateBeforeEarlyMedia; protected boolean reliableEarlyMedia; protected SIPIncomingCall(final ExecutionContext context, final SipServletRequest req) { super(context, req); setRemoteSDP(SIPHelper.getRawContentWOException(req)); } @Override protected JoinDelegate createJoinDelegate(final Direction direction) { JoinDelegate retval = null; if (isNoAnswered()) { retval = new Media2NIJoinDelegate(this); } else if (isAnswered()) { retval = new Media2AIJoinDelegate(this); } else { throw new IllegalStateException("The SIPCall state is " + getSIPCallState()); } return retval; } @Override protected JoinDelegate createJoinDelegate(final SIPCallImpl other, final JoinType type, final Direction direction) { JoinDelegate retval = null; if (type == JoinType.DIRECT) { if (isNoAnswered()) { if (other.isNoAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectNI2NOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectNI2NIJoinDelegate(this, (SIPIncomingCall) other, direction, (SIPIncomingCall) other); } } else if (other.isAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectNI2AOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectNI2AIJoinDelegate(this, (SIPIncomingCall) other, direction, (SIPIncomingCall) other); } } } else if (isAnswered()) { if (other.isNoAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectAI2NOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectNI2AIJoinDelegate((SIPIncomingCall) other, this, direction, (SIPIncomingCall) other); } } else if (other.isAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectAI2AOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectAI2AIJoinDelegate(this, (SIPIncomingCall) other, direction, (SIPIncomingCall) other); } } } } else { retval = new BridgeJoinDelegate(this, other, direction, type, other); } return retval; } protected JoinDelegate createJoinDelegate(final Call[] others, final JoinType type, final Direction direction) { if (this.isNoAnswered() && type == JoinType.DIRECT) { JoinDelegate retval = null; List<SIPCallImpl> candidates = new LinkedList<SIPCallImpl>(); for (Call call : others) { candidates.add((SIPCallImpl) call); } retval = new DirectNI2MultipleNOJoinDelegate(type, direction, this, Utils.suppressEarlyMedia(getApplicationContext()), candidates); return retval; } else { return super.createJoinDelegate(others, type, direction); } } @Override public synchronized void onEvent(final SdpPortManagerEvent event) { if (getSIPCallState() == SIPCall.State.PROGRESSING) { if (event.getEventType() == SdpPortManagerEvent.OFFER_GENERATED || event.getEventType() == SdpPortManagerEvent.ANSWER_GENERATED) { final byte[] sdp = event.getMediaServerSdp(); this.setLocalSDP(sdp); final SipServletResponse res = getSipInitnalRequest().createResponse(SipServletResponse.SC_SESSION_PROGRESS); try { res.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); try { res.sendReliably(); reliableEarlyMedia = true; } catch (Rel100Exception ex) { LOG.debug("Can't send reliably."); //TODO should send it unreliably? res.send(); } if (!reliableEarlyMedia) { setSIPCallState(oldStateBeforeEarlyMedia); notifyAll(); } } catch (final Exception e) { LOG.warn("Can't send early media response.", e); acceptEarlyMediaException = e; setSIPCallState(oldStateBeforeEarlyMedia); notifyAll(); } } else if (event.getEventType() == SdpPortManagerEvent.ANSWER_PROCESSED) { if(reliableEarlyMedia) { setSIPCallState(SIPCall.State.PROGRESSED); } else { setSIPCallState(oldStateBeforeEarlyMedia); } notifyAll(); } } else { super.onEvent(event); } } protected synchronized void doCancel(SipServletRequest req) { if (isTerminated()) { if (LOG.isDebugEnabled()) { LOG.debug("Receiving Cancel, but is already terminated. callID:" + (getSipSession() != null ? getSipSession().getCallId() : "")); } } else if (isNoAnswered()) { if (LOG.isDebugEnabled()) { LOG.debug("Receiving Cancel, not answered. terminating, callID" + (getSipSession() != null ? getSipSession().getCallId() : "")); } this.setSIPCallState(SIPCall.State.DISCONNECTED); terminate(CallCompleteEvent.Cause.CANCEL, null, null); } else { if (LOG.isDebugEnabled()) { LOG.debug("Receiving Cancel, but is already answered. terminating, callID" + (getSipSession() != null ? getSipSession().getCallId() : "")); } this.setSIPCallState(SIPCall.State.DISCONNECTED); terminate(CallCompleteEvent.Cause.CANCEL, null, null); } try { req.createResponse(200).send(); } catch (IOException e) { LOG.warn("Exception when sending back response for CANCEL." + req); } } protected synchronized void doInvite(final Map<String, String> headers) throws IOException { if (_cstate == SIPCall.State.INVITING) { setSIPCallState(SIPCall.State.RINGING); final SipServletResponse res = _invite.createResponse(SipServletResponse.SC_RINGING); SIPHelper.addHeaders(res, headers); res.send(); } } protected synchronized void doInviteWithEarlyMedia(final Map<String, String> headers) throws MediaException, SignalException { if(!SIPHelper.support100rel(getSipInitnalRequest())) { LOG.error("Request doesn't support 100rel, can't acceptWithEarlyMedia."); throw new SignalException("Request doesn't support 100rel, can't acceptWithEarlyMedia." + this); } if (_cstate == SIPCall.State.INVITING || _cstate == SIPCall.State.RINGING) { oldStateBeforeEarlyMedia = _cstate; setSIPCallState(SIPCall.State.PROGRESSING); try { processSDPOffer(getSipInitnalRequest()); } catch (MediaException ex) { acceptEarlyMediaException = ex; setSIPCallState(oldStateBeforeEarlyMedia); } while (!this.isTerminated() && _cstate == SIPCall.State.PROGRESSING) { try { wait(20000); } catch (final InterruptedException e) { // ignore } } if (acceptEarlyMediaException != null || (_cstate != SIPCall.State.PROGRESSED && _cstate != oldStateBeforeEarlyMedia)) { if (acceptEarlyMediaException != null) { if (acceptEarlyMediaException instanceof SignalException) { throw (SignalException) acceptEarlyMediaException; } else { throw (MediaException) acceptEarlyMediaException; } } else { throw new SignalException("Can't acceptWithEarlyMedia." + this); } } } } protected synchronized void doPrack(final SipServletRequest req) throws IOException { final byte[] content = SIPHelper.getRawContentWOException(req); if (content != null) { setRemoteSDP(content); } if (_joinDelegate != null) { try { _joinDelegate.doPrack(req, this, null); } catch (Exception ex) { LOG.error("Exception when processing PRACK", ex); } } else { final SipServletResponse res = req.createResponse(SipServletResponse.SC_OK); if (_cstate == SIPCall.State.PROGRESSING) { if (content != null && SIPHelper.getRawContentWOException(_invite) == null) { try { processSDPAnswer(req); } catch (MediaException ex) { acceptEarlyMediaException = ex; setSIPCallState(oldStateBeforeEarlyMedia); } } else { setSIPCallState(SIPCall.State.PROGRESSED); } notifyAll(); } res.send(); } } protected boolean _acceptedWithEarlyMedia = false; protected boolean _rejected = false; protected boolean _redirected = false; protected boolean _accepted = false; protected boolean _proxied = false; @Override public synchronized boolean isAcceptedWithEarlyMedia() { return _acceptedWithEarlyMedia; } @Override public synchronized boolean isRedirected() { return _redirected; } @Override public synchronized boolean isRejected() { return _rejected; } @Override public synchronized boolean isAccepted() { return _accepted; } @Override public synchronized boolean isProxied() { return _proxied; } protected synchronized boolean isProcessed() { return !(getSIPCallState() == SIPCall.State.RINGING || getSIPCallState() == SIPCall.State.INITIALIZED || getSIPCallState() == SIPCall.State.PROGRESSED || getSIPCallState() == SIPCall.State.PROGRESSING || getSIPCallState() == SIPCall.State.INVITING); } @Override public void acceptWithEarlyMedia() throws SignalException, MediaException { this.acceptWithEarlyMedia((Map<String, String>) null); } @Override public void redirect(final Endpoint other) throws SignalException { this.redirect(other, null); } @Override public void reject(final Reason reason) throws SignalException { this.reject(reason, null); } @Override public void answer() { this.answer((Map<String, String>) null); } @Override public synchronized void accept(final Map<String, String> headers) throws SignalException { checkState(); _accepted = true; try { ((SIPIncomingCall) this).doInvite(headers); return; } catch (final Exception e) { throw new SignalException(e); } } @Override public synchronized void acceptWithEarlyMedia(final Map<String, String> headers) throws SignalException, MediaException { checkState(); _accepted = true; _acceptedWithEarlyMedia = true; try { ((SIPIncomingCall) this).doInviteWithEarlyMedia(headers); return; } catch (final Exception e) { if (e instanceof SignalException) { throw (SignalException) e; } else if (e instanceof MediaException) { throw (MediaException) e; } else { throw new SignalException(e); } } } @Override public void answer(final Map<String, String> headers) throws SignalException { if (!_accepted) { accept(headers); } final Joint joint = this.join(); while (!joint.isDone()) { try { joint.get(); } catch (final InterruptedException e) { // ignore } catch (final ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof SignalException) { throw (SignalException) cause; } throw new SignalException(cause); } } return; } @Override public synchronized void redirect(final Endpoint o, final Map<String, String> headers) throws SignalException { checkState(); _redirected = true; setSIPCallState(SIPCall.State.REDIRECTED); terminate(CallCompleteEvent.Cause.REDIRECT, null, headers); if (o instanceof SIPEndpoint) { final SipServletResponse res = _invite.createResponse(SipServletResponse.SC_MOVED_TEMPORARILY); res.setHeader("Contact", ((SIPEndpoint) o).getURI().toString()); SIPHelper.addHeaders(res, headers); try { res.send(); } catch (final IOException e) { throw new SignalException(e); } } else { throw new IllegalArgumentException("Unable to redirect the call to a non-SIP participant."); } } @Override public synchronized void reject(final Reason reason, final Map<String, String> headers) throws SignalException { checkState(); _rejected = true; setSIPCallState(SIPCall.State.REJECTED); terminate(CallCompleteEvent.Cause.DECLINE, null, headers); try { final SipServletResponse res = _invite.createResponse(reason == null ? Reason.DECLINE.getCode() : reason .getCode()); SIPHelper.addHeaders(res, headers); res.send(); } catch (final IOException e) { throw new SignalException(e); } } protected synchronized void checkState() { if (isProcessed()) { throw new IllegalStateException("Event is already processed and can not be processed."); } } @Override public Call getSource() { return this; } @Override public void accept() throws SignalException { accept((Map<String, String>) null); } @Override public void acceptWithEarlyMedia(Observer... observer) throws SignalException, MediaException { addObserver(observer); acceptWithEarlyMedia(); } @Override public void accept(Observer... observer) throws SignalException { addObserver(observer); accept(); } @Override public void answer(Observer... observer) throws SignalException, MediaException { addObserver(observer); answer(); } @Override public void proxyTo(boolean recordRoute, boolean parallel, Endpoint... destinations) throws SignalException { proxyTo(recordRoute, parallel, null, destinations); } @Override public synchronized void proxyTo(boolean recordRoute, boolean parallel, Map<String, String> headers, Endpoint... destinations) { checkState(); _proxied = true; setSIPCallState(SIPCall.State.PROXIED); SIPHelper.proxyTo(getApplicationContext().getSipFactory(), _invite, headers, recordRoute, parallel, destinations); } @Override public void setAsync(boolean async) { } @Override public boolean isAsync() { return false; } @Override public byte[] getJoinSDP() throws IOException { if (!isAnswered()) { return _invite.getRawContent(); } else { _invite = getSipSession().createRequest("INVITE"); _invite.send(); return null; } } @Override public void processSDPAnswer(byte[] sdp) throws IOException { if (!isAnswered()) { SipServletResponse newRes = _invite.createResponse(SipServletResponse.SC_OK); newRes.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); newRes.send(); } else if (_inviteResponse != null) { SipServletRequest ack = _inviteResponse.createAck(); ack.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); ack.send(); } else { throw new IllegalStateException("SIPIncomingCall, answered and no re-INVITE response."); } } @Override public byte[] processSDPOffer(byte[] sdp) throws IOException { if (!isAnswered()) { final SipServletResponse newRes = _invite.createResponse(SipServletResponse.SC_OK); newRes.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); newRes.send(); return _invite.getRawContent(); } else { _invite = getSipSession().createRequest("INVITE"); _invite.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); _invite.send(); return null; } } @Override public void setContinueRouting(SIPCall origCall) { throw new UnsupportedOperationException("incoming call doesn't support this method."); } protected int getGlareReInivteDelay() { Random random = new Random(); int delay = (random.nextInt(201)) * 10; return delay; } public boolean isEarlyMediaSupported() { return SIPHelper.support100rel(getSipInitnalRequest()); } }