/** * 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.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.media.mscontrol.MediaEventListener; import javax.media.mscontrol.MediaObject; import javax.media.mscontrol.MediaSession; import javax.media.mscontrol.MsControlException; import javax.media.mscontrol.MsControlFactory; import javax.media.mscontrol.Parameter; import javax.media.mscontrol.Parameters; import javax.media.mscontrol.join.Joinable; import javax.media.mscontrol.join.Joinable.Direction; import javax.media.mscontrol.join.JoinableStream; import javax.media.mscontrol.join.JoinableStream.StreamType; import javax.media.mscontrol.mixer.MediaMixer; import javax.media.mscontrol.networkconnection.NetworkConnection; import javax.media.mscontrol.networkconnection.SdpPortManagerEvent; import javax.sdp.MediaDescription; import javax.sdp.Origin; import javax.sdp.SdpException; import javax.sdp.SdpFactory; import javax.sdp.SessionDescription; import javax.servlet.sip.SipApplicationSession; import javax.servlet.sip.SipServletMessage; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import javax.servlet.sip.SipSession; import javax.servlet.sip.SipSessionsUtil; import org.apache.log4j.Logger; import com.voxeo.moho.ApplicationContextImpl; import com.voxeo.moho.Call; import com.voxeo.moho.CallImpl; import com.voxeo.moho.CallableEndpoint; import com.voxeo.moho.Endpoint; import com.voxeo.moho.JoinData; import com.voxeo.moho.JoineeData; import com.voxeo.moho.Joint; import com.voxeo.moho.JointImpl; import com.voxeo.moho.MediaException; import com.voxeo.moho.MediaService; import com.voxeo.moho.OutgoingCall; import com.voxeo.moho.Participant; import com.voxeo.moho.ParticipantContainer; import com.voxeo.moho.SettableJointImpl; import com.voxeo.moho.SignalException; import com.voxeo.moho.Unjoint; import com.voxeo.moho.UnjointImpl; import com.voxeo.moho.common.event.MohoCallCompleteEvent; import com.voxeo.moho.common.event.MohoJoinCompleteEvent; import com.voxeo.moho.common.event.MohoUnjoinCompleteEvent; import com.voxeo.moho.common.util.InheritLogContextRunnable; import com.voxeo.moho.common.util.Utils; import com.voxeo.moho.event.CallCompleteEvent; import com.voxeo.moho.event.JoinCompleteEvent; import com.voxeo.moho.event.JoinCompleteEvent.Cause; import com.voxeo.moho.event.UnjoinCompleteEvent; import com.voxeo.moho.media.GenericMediaService; import com.voxeo.moho.media.dialect.MediaDialect; import com.voxeo.moho.remote.sipbased.RemoteJoinOutgoingCall; import com.voxeo.moho.remotejoin.RemoteParticipant; import com.voxeo.moho.spi.ExecutionContext; import com.voxeo.moho.util.ParticipantIDParser; import com.voxeo.moho.util.SDPUtils; import com.voxeo.moho.util.SessionUtils; public abstract class SIPCallImpl extends CallImpl implements SIPCall, MediaEventListener<SdpPortManagerEvent>, ParticipantContainer { private static final Logger LOG = Logger.getLogger(SIPCallImpl.class); protected static String Att_REINVITE_HEADERS = "Att_REINVITE_HEADERS"; protected static String Att_REINVITE_ATTRIBUTES = "Att_REINVITE_ATTRIBUTES"; protected SIPCall.State _cstate; protected SIPEndpoint _address; protected SipServletRequest _invite; protected byte[] _remoteSDP; protected byte[] _localSDP; protected SipSession _signal; protected MediaSession _media; protected NetworkConnection _network; protected MediaService<Call> _service; protected JoinDelegate _joinDelegate; protected JoinDelegate _oldJoinDelegate; protected SIPCallDelegate _callDelegate; protected JoineeData _joinees = new JoineeData(); protected boolean _operationInProcess; protected String _replacesHeader; protected Exception _exception; protected JoinData _bridgeJoiningPeer; protected MediaMixer _multiplejoiningMixer; // TODO: join to MediaGroup protected MediaMixer _multiplejoiningMixerForMedGrop; protected Lock mediaServiceLock = new ReentrantLock(); protected Queue<JoinRequest> _joinQueue = new LinkedList<JoinRequest>(); protected SipServletResponse _inviteResponse; protected boolean reInvitingRemote; protected boolean needReInivteRemote; protected boolean processingReinvite; protected boolean pendingReinvite; protected Origin previousOrigin; protected SIPCallImpl(final ExecutionContext context, final SipServletRequest req) { super(context); _caller = new SIPEndpointImpl(context, req.getFrom()); _callee = new SIPEndpointImpl(context, req.getTo()); _invite = req; // process Replaces header. final SipSessionsUtil sessionUtil = (SipSessionsUtil) _invite.getSession().getServletContext() .getAttribute("javax.servlet.sip.SipSessionsUtil"); if (sessionUtil != null) { final SipSession peerSession = sessionUtil.getCorrespondingSipSession(_invite.getSession(), "Replaces"); if (peerSession != null) { final SIPCallImpl call = (SIPCallImpl) SessionUtils.getParticipant(peerSession); final SipSession replacedSession = ((SIPCallImpl) call.getLastPeer()).getSipSession(); final String callId = replacedSession.getCallId(); final String toTag = replacedSession.getRemoteParty().getParameter("tag"); final String fromTag = replacedSession.getLocalParty().getParameter("tag"); // the format // Replaces: call-id;to-tag=7743;from-tag=6472 final StringBuilder sb = new StringBuilder(); sb.append(callId); sb.append(";to-tag="); sb.append(toTag); sb.append(";from-tag="); sb.append(fromTag); _replacesHeader = sb.toString(); } } _signal = req.getSession(); _address = new SIPEndpointImpl((ApplicationContextImpl) getApplicationContext(), _signal.getRemoteParty()); SessionUtils.setEventSource(_signal, this); context.addCall(this); _cstate = SIPCall.State.INVITING; } protected SIPCallImpl(final ExecutionContext context) { super(context); _cstate = SIPCall.State.INITIALIZED; } @Override public int hashCode() { return "SIPCall".hashCode() + getId().hashCode(); } @Override public boolean equals(final Object o) { if (!(o instanceof SIPCall)) { return false; } if (this == o) { return true; } return this.getId().equals(((SIPCall) o).getId()); } @Override public String toString() { return String.format("%s[sipsessionid=%s id=%s callstate=%s]", getClass().getSimpleName(), _signal.getId(), _id, _cstate); } public String useReplacesHeader() { final String ret = _replacesHeader; _replacesHeader = null; return ret; } @Override public Call.State getCallState() { switch (_cstate) { case INITIALIZED: return Call.State.INITIALIZED; case PROGRESSING: case PROGRESSED: return Call.State.INPROGRESS; case INVITING: case RINGING: case ANSWERING: return Call.State.ACCEPTED; case ANSWERED: return Call.State.CONNECTED; case FAILED: case REJECTED: return Call.State.FAILED; case DISCONNECTED: case REDIRECTED: return Call.State.DISCONNECTED; } return null; } @Override public SIPCall.State getSIPCallState() { return _cstate; } @Override public MediaService<Call> getMediaService(final boolean reinvite) throws IllegalStateException, MediaException { if (getSIPCallState() != SIPCall.State.ANSWERED && getSIPCallState() != SIPCall.State.RINGING && getSIPCallState() != SIPCall.State.PROGRESSED) { throw new IllegalStateException("The call has not been answered or there was no progress in the call"); } mediaServiceLock.lock(); try { if (_network == null) { if (reinvite) { JoinCompleteEvent joinResult = null; try { joinResult = this.join(Direction.DUPLEX).get(); } catch (final Exception e) { throw new MediaException(e); } if(joinResult.getCause() != JoinCompleteEvent.Cause.JOINED) { throw new MediaException("Join result in error result:" + joinResult.getCause()); } } else { throw new IllegalStateException("the call is Direct mode but reinvite is false"); } } try { if (_service == null) { Parameters params = _media.createParameters(); _service = _context.getMediaServiceFactory().create((Call) this, _media, params); JoinDelegate.bridgeJoin(this, _service.getMediaGroup()); } } catch (final Exception e) { throw new MediaException(e); } return _service; } finally { mediaServiceLock.unlock(); } } @Override public SipSession getSipSession() { return _signal; } public MediaObject getMediaObject() { return _network; } @Override public void disconnect() { this.disconnect(false, CallCompleteEvent.Cause.NEAR_END_DISCONNECT, null, null); } @Override public void hangup(Map<String, String> headers) { this.disconnect(false, CallCompleteEvent.Cause.NEAR_END_DISCONNECT, null, headers); } @Override public Participant[] getParticipants() { if (this.getJoiningPeer() != null) { Participant[] participants = _joinees.getJoinees(); Participant[] allParticipants = Arrays.copyOf(participants, participants.length + 1); allParticipants[participants.length] = this.getJoiningPeer().getParticipant(); return allParticipants; } return _joinees.getJoinees(); } @Override public Participant[] getParticipants(final Direction direction) { if (this.getJoiningPeer() != null) { Participant[] participants = _joinees.getJoinees(direction); Participant[] allParticipants = Arrays.copyOf(participants, participants.length + 1); allParticipants[participants.length] = this.getJoiningPeer().getParticipant(); return allParticipants; } return _joinees.getJoinees(direction); } @Override public JoinType getJoinType(Participant participant) { return _joinees.getJoinType(participant); } @Override public Direction getDirection(Participant participant) { return _joinees.getDirection(participant); } @Override public JoinableStream getJoinableStream(final StreamType arg0) throws MediaException, IllegalStateException { if (_network == null) { throw new IllegalStateException(); } try { return _network.getJoinableStream(arg0); } catch (final MsControlException e) { throw new MediaException(e); } } @Override public JoinableStream[] getJoinableStreams() throws MediaException, IllegalStateException { if (_network == null) { throw new IllegalStateException(); } try { return _network.getJoinableStreams(); } catch (final MsControlException e) { throw new MediaException(e); } } public Unjoint unjoin(final Participant other) { Unjoint task = new UnjointImpl(_context.getExecutor(), new Callable<UnjoinCompleteEvent>() { @Override public UnjoinCompleteEvent call() throws Exception { return doUnjoin(other, true); } }); return task; } public Unjoint unjoin(final Participant other, final boolean isInitiator) { Unjoint task = new UnjointImpl(_context.getExecutor(), new Callable<UnjoinCompleteEvent>() { @Override public UnjoinCompleteEvent call() throws Exception { return doUnjoin(other, isInitiator); } }); return task; } @Override public synchronized MohoUnjoinCompleteEvent doUnjoin(final Participant p, boolean initiator) throws Exception { MohoUnjoinCompleteEvent event = null; if (!isAnswered()) { event = new MohoUnjoinCompleteEvent(SIPCallImpl.this, p, UnjoinCompleteEvent.Cause.NOT_JOINED, initiator); SIPCallImpl.this.dispatch(event); return event; } if (!_joinees.contains(p)) { event = new MohoUnjoinCompleteEvent(SIPCallImpl.this, p, UnjoinCompleteEvent.Cause.NOT_JOINED, initiator); SIPCallImpl.this.dispatch(event); return event; } LOG.debug(String.format("%s unjoining %s, initiator %s", this, p, initiator)); // wait if processing re-INVITE from remote waitProcessReInvite(); Participant participant = p; Participant local = this; try { JoinData joinData = _joinees.remove(p); Participant other = joinData.getParticipant(); if (other instanceof Call) { synchronized (_peers) { _peers.remove(other); } } if (other.getMediaObject() instanceof Joinable) { if (initiator) { if (joinData.getRealJoined() == null) { JoinDelegate.bridgeUnjoin(this, other); } else { JoinDelegate.bridgeUnjoin(joinData.getRealJoined(), other); } } } if (initiator) { ((ParticipantContainer) other).unjoin(SIPCallImpl.this, false); } // for remote unjoin if (p instanceof RemoteParticipant) { participant = this.getApplicationContext().getParticipant(((RemoteParticipant) p).getRemoteParticipantID()); } if (this instanceof RemoteParticipant) { local = this.getApplicationContext().getParticipant(((RemoteParticipant) this).getRemoteParticipantID()); } event = new MohoUnjoinCompleteEvent(local, participant, UnjoinCompleteEvent.Cause.SUCCESS_UNJOIN, initiator); } catch (final Exception e) { LOG.error("Exception when doing unjoin.", e); event = new MohoUnjoinCompleteEvent(local, participant, UnjoinCompleteEvent.Cause.FAIL_UNJOIN, e, true); throw e; } finally { if (event == null) { event = new MohoUnjoinCompleteEvent(local, participant, UnjoinCompleteEvent.Cause.FAIL_UNJOIN, initiator); } SIPCallImpl.this.dispatch(event); if(_joinees.getJoinees().length == 0 && _network == null) { _callDelegate = null; } } return event; } @Override public Joint join(final CallableEndpoint other, final JoinType type, final Direction direction, final Map<String, String> headers) { Participant p = null; try { p = other.createCall(getAddress(), headers, this); } catch (final Exception e) { LOG.error(e); return new JointImpl(_context.getExecutor(), new JointImpl.DummyJoinWorker(SIPCallImpl.this, p, e)); } return join(p, type, direction); } @Override public synchronized Joint join(final Direction direction) { return this.join(direction, null); } public synchronized Joint join(final Direction direction, SettableJointImpl joint) { if (isTerminated()) { throw new IllegalStateException("already terminated."); } LOG.debug(String.format("%s joining to media, direction %s", this, direction.toString())); //wait if processing re-INVITE from remote. waitProcessReInvite(); if (_operationInProcess) { if (_joinDelegate != null && !(_joinDelegate instanceof BridgeJoinDelegate || _joinDelegate instanceof MultipleNOBridgeJoinDelegate || _joinDelegate instanceof OtherParticipantJoinDelegate || _joinDelegate instanceof LocalRemoteJoinDelegate || _joinDelegate instanceof RemoteLocalJoinDelegate)) { if (joint == null) { joint = new SettableJointImpl(); } JoinRequest joinReq = new JoinRequest(direction, joint); _joinQueue.add(joinReq); return joint; } } _operationInProcess = true; try { if (_joinDelegate != null) { _oldJoinDelegate = _joinDelegate; _joinDelegate = null; } _joinDelegate = createJoinDelegate(direction); if (joint == null) { joint = new SettableJointImpl(); } _joinDelegate.setSettableJoint(joint); _joinDelegate.doJoin(); return joint; } catch (Exception ex) { // TODO throw new RuntimeException(ex); } } public void joinDone(final Participant participant, final JoinDelegate delegate) { if (delegate != _joinDelegate) { return; } setJoiningPeer(null); if (_joinDelegate.getPeer() != null) { if (JoinType.isBridge(_joinDelegate.getJoinType())) { _callDelegate = new SIPCallBridgeDelegate(); } else { _callDelegate = new SIPCallDirectDelegate(); } } else { _callDelegate = new SIPCallMediaDelegate(); } if (_oldJoinDelegate != null && !(_oldJoinDelegate instanceof MultipleNOBridgeJoinDelegate)) { JoinCompleteEvent.Cause cause = _joinDelegate.getCause(); Exception exception = _joinDelegate.getException(); _joinDelegate = _oldJoinDelegate; _oldJoinDelegate = null; if (cause == JoinCompleteEvent.Cause.JOINED) { try { _operationInProcess = true; LOG.debug("starting the old join delegte."); _joinDelegate.doJoin(); } catch (Exception e) { LOG.error(e.getMessage(), e); _joinDelegate.done(JoinCompleteEvent.Cause.ERROR, e); } } else { _joinDelegate.done(cause, exception); } return; } else { _joinDelegate = null; _oldJoinDelegate = null; _operationInProcess = false; reInvitingRemote = false; } _context.getExecutor().execute(new Runnable() { @Override public void run() { synchronized (SIPCallImpl.this) { SIPCallImpl.this.notifyAll(); } } }); } public synchronized void continueQueuedJoin() { JoinRequest queuedJoin = _joinQueue.poll(); if (queuedJoin != null) { if (queuedJoin.getCalls() != null) { this.join(queuedJoin.getJoint(), queuedJoin.getType(), queuedJoin.isForce(), queuedJoin.getDirection(), queuedJoin.isDtmfPassThrough(), queuedJoin.getCalls()); } else if (queuedJoin.getPeer() != null) { this.join(queuedJoin.getPeer(), queuedJoin.getType(), queuedJoin.isForce(), queuedJoin.getDirection(), queuedJoin.isDtmfPassThrough(), queuedJoin.getJoint()); } else { this.join(queuedJoin.getDirection(), queuedJoin.getJoint()); } } } public synchronized int queuedJoinSize() { return _joinQueue.size(); } @Override public synchronized Joint join(final Participant other, final JoinType type, final Direction direction) { return this.join(other, type, false, direction); } @Override public synchronized Joint join(final Participant other, final JoinType type, final boolean force, final Direction direction) { return this.join(other, type, force, direction, true); } @Override public synchronized Joint join(final Participant other, final JoinType type, final boolean force, final Direction direction, boolean dtmfPassThrough) { return join(other, type, force, direction, dtmfPassThrough, null); } @Override public synchronized Joint join(JoinType type, boolean force, Direction direction, Map<String, String> headers, boolean dtmfPassThrough, CallableEndpoint... others) { if (others == null || others.length == 0 || type == null || direction == null) { throw new IllegalArgumentException("others should not be empty."); } List<Call> calls = new ArrayList<Call>(); for (CallableEndpoint other : others) { calls.add(other.createCall(getAddress(), headers, this)); } if(calls.size() ==1) { return this.join(calls.get(0), type, force, direction, dtmfPassThrough); } return join(type, force, direction, dtmfPassThrough, calls.toArray(new Call[calls.size()])); } @Override public synchronized Joint join(JoinType type, boolean force, Direction direction, boolean dtmfPassThrough, Call... others) { return this.join(null, type, force, direction, dtmfPassThrough, others); } protected synchronized Joint join(SettableJointImpl joint, JoinType type, boolean force, Direction direction, boolean dtmfPassThrough, Call... others) { if (isTerminated()) { throw new IllegalStateException("This call is already terminated."); } if(others.length ==1) { return this.join(others[0], type, force, direction, dtmfPassThrough); } for (Call other : others) { if (other.equals(this)) { throw new IllegalArgumentException("Can't join to itself."); } if (((SIPCallImpl) other).isAnswered()) { throw new IllegalArgumentException("Doesn't support join to multiple answered calls now."); } } LOG.debug(String.format("%s joining to %s, JoinType %s, force %s, Direction %s, dtmfPassThrough %s", this, others, type, force, direction.toString(), dtmfPassThrough)); //wait if processing re-INVITE from remote. waitProcessReInvite(); if (_operationInProcess) { if (joint == null) { joint = new SettableJointImpl(); } JoinRequest joinReq = new JoinRequest(others, type, direction, force, dtmfPassThrough, joint); _joinQueue.add(joinReq); return joint; } _operationInProcess = true; try { ExecutionException ex = JoinDelegate.checkJoinStrategy(this, type, force); if (joint == null) { joint = new SettableJointImpl(); } if (ex == null) { _joinDelegate = createJoinDelegate(others, type, direction); _joinDelegate.setSettableJoint(joint); _joinDelegate.setDtmfPassThrough(dtmfPassThrough); for(Call other : others){ ((SIPCallImpl)other).startJoin(this, _joinDelegate); } _joinDelegate.doJoin(); } else { // dispatch BUSY event JoinCompleteEvent joinCompleteEvent = new MohoJoinCompleteEvent(this, others[0], Cause.BUSY, ex, true); dispatch(joinCompleteEvent); joint.done(ex); } return joint; } catch (Exception ex) { if (_joinDelegate != null) { _joinDelegate.done(Cause.ERROR, ex); } throw new RuntimeException(ex); } } protected synchronized Joint join(final Participant other, final JoinType type, final boolean force, final Direction direction, boolean dtmfPassThrough, SettableJointImpl joint) { if (isTerminated()) { throw new IllegalStateException("This call is already terminated."); } if (other.equals(this)) { throw new IllegalStateException("Can't join to itself."); } LOG.debug(String.format("%s joining to %s, JoinType %s, force %s, Direction %s, dtmfPassThrough %s", this, other, type, force, direction.toString(), dtmfPassThrough)); //wait if processing re-INVITE from remote. waitProcessReInvite(); if (_operationInProcess) { // this is used for the case that receive 183 from not-answered outgoing // call if (other instanceof SIPCallImpl) { this.setJoiningPeer(new JoinData(other, direction, type)); } if (joint == null) { joint = new SettableJointImpl(); } JoinRequest joinReq = new JoinRequest(other, type, direction, force, dtmfPassThrough, joint); _joinQueue.add(joinReq); return joint; } _operationInProcess = true; try { if (other instanceof SIPCallImpl) { return doJoin((SIPCallImpl) other, type, force, direction, dtmfPassThrough, joint); } else if (other instanceof RemoteParticipant) { return doJoin((RemoteParticipant) other, type, force, direction, dtmfPassThrough, joint); } else { return doJoin(other, type, false, direction, dtmfPassThrough, joint); } } catch (Exception ex) { if (_joinDelegate != null) { _joinDelegate.done(Cause.ERROR, ex); } throw new RuntimeException(ex); } } public synchronized void onEvent(final SdpPortManagerEvent event) { LOG.debug("Receive SdpPortManagerEvent:" + event); if (isTerminated()) { LOG.debug(this + " is already terminated."); return; } if(event.getEventType() == SdpPortManagerEvent.NETWORK_STREAM_FAILURE) { LOG.info(this + " detected media failure, hanguping call."); hangup(); } else { try { if (_joinDelegate != null) { _joinDelegate.doSdpEvent(event); } else if (_callDelegate != null) { _callDelegate.handleSdpEvent(this, event); } else { LOG.debug("The SDP event will be discarded."); } } catch (final Exception e) { LOG.warn("Exception when processing SdpPortManagerEvent:" + event, e); } } } protected void doBye(final SipServletRequest req, final Map<String, String> headers) { if (LOG.isDebugEnabled()) { LOG.debug(this + "Processing BYE request."); } synchronized (this) { if (isTerminated()) { LOG.debug(this + " is already terminated."); try { req.createResponse(SipServletResponse.SC_OK).send(); } catch (final Exception e) { LOG.warn("Excetion sending back SIP response", e); } return; } else { this.setSIPCallState(SIPCall.State.DISCONNECTED); } } terminate(CallCompleteEvent.Cause.DISCONNECT, null, headers); try { req.createResponse(SipServletResponse.SC_OK).send(); } catch (final Exception e) { LOG.warn("Excetion sending back SIP response", e); } } protected synchronized void doAck(final SipServletRequest req) throws Exception { if (isTerminated()) { LOG.debug(this + " is already terminated."); return; } if (!SIPHelper.isAck(req)) { LOG.debug("The SIP request isn't ACK."); return; } if (LOG.isDebugEnabled()) { LOG.debug(this + "Processing ACK."); } final byte[] content = SIPHelper.getRawContentWOException(req); if (content != null) { setRemoteSDP(content); } if(pendingReinvite) { pendingReinvite = false; LOG.debug(this +" process pending re-INVITE complete."); notifyAll(); return; } if (_joinDelegate != null) { _joinDelegate.doAck(req, this); } else if (_callDelegate != null && isAnswered()) { _callDelegate.handleAck(this, req); processingReinvite = false; LOG.debug(this +" process re-INVITE complete."); notifyAll(); } else { LOG.debug("The SIP message will be discarded."); } } protected synchronized void doReinvite(final SipServletRequest req, final Map<String, String> headers) throws Exception { if (isTerminated()) { LOG.debug(this + " is already terminated."); return; } if (!SIPHelper.isReinvite(req)) { LOG.debug("The SIP request isn't Re-INVITE."); return; } if (LOG.isDebugEnabled()) { LOG.debug(this + "Processing re-INVITE."); } //joining and sent out re-INVITE if(_operationInProcess && reInvitingRemote) { req.createResponse(SipServletResponse.SC_REQUEST_PENDING).send(); return; } //joining and not send out re-INVITE while(_operationInProcess && isAnswered()) { pendingReinvite = true; LOG.debug(this + "operation is in progress, waiting it complete."); wait(); //sent out re-INVITE when joining if(needReInivteRemote) { SipServletResponse resp = req.createResponse(SipServletResponse.SC_OK); resp.setContent(SDPUtils.formulateSDP(this, getLocalSDP()), "application/sdp"); resp.send(); return; } pendingReinvite = false; if(!isAnswered()) { req.createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send(); LOG.warn(this + " is already terminated. return error response for re-INVITE."); return; } } final byte[] content = SIPHelper.getRawContentWOException(req); if (content != null) { setRemoteSDP(content); } if (_callDelegate != null) { _callDelegate.handleReinvite(this, req, headers); processingReinvite = true; } else { LOG.debug("_callDelegate is null, operation in progress, returning blackhole SDP response."); SipServletResponse resp = req.createResponse(SipServletResponse.SC_OK); resp.setContent(SDPUtils.makeBlackholeSDP(SDPUtils.formulateSDP(this, getLocalSDP())), "application/sdp"); resp.send(); } } protected synchronized void doUpdate(final SipServletRequest req, final Map<String, String> headers) throws Exception { if (isTerminated()) { LOG.debug(this + " is already terminated."); return; } if (LOG.isDebugEnabled()) { LOG.debug(this + "Processing UPDATE."); } final byte[] content = SIPHelper.getRawContentWOException(req); if (content != null) { setRemoteSDP(content); } if (_joinDelegate != null) { _joinDelegate.doUpdate(req, this, headers); } if (_callDelegate != null) { _callDelegate.handleUpdate(this, req, headers); } else { LOG.debug("CallDelegate is null, the SIP message will be discarded." + req); } } protected int getGlareReInivteDelay() { return 100; } protected synchronized void doResponse(final SipServletResponse res, final Map<String, String> headers) throws Exception { if (isTerminated()) { LOG.debug(this + " is already terminated."); return; } if (LOG.isDebugEnabled()) { LOG.debug(this + "Processing response."); } if (SIPHelper.isInvite(res)) { if (res.getStatus() == SipServletResponse.SC_REQUEST_PENDING && isAnswered()) { // glare re-INVITE. send again after a random delay between 2.1 and 4 // second. LOG.debug(String.format("%s received 491 response, glare re-INVITE, will retry re-INVITE later.", this)); reInvitingRemote = false; final Map<String, String> logContexts = Utils.getCurrentLogContexts(); ((ApplicationContextImpl) _context).getScheduledEcutor().schedule(new InheritLogContextRunnable() { @SuppressWarnings("unchecked") @Override public void run() { Utils.inheritLogContexts(logContexts); try { SIPCallImpl.this.reInviteRemote(res.getRequest().getContent(), (Map<String, String>) res.getRequest() .getAttribute(Att_REINVITE_HEADERS), (Map<String, String>) res.getRequest().getAttribute(Att_REINVITE_ATTRIBUTES)); } catch (Exception e) { LOG.debug("Exception when sending re-INVITE", e); SIPCallImpl.this.fail(e); } finally{ Utils.clearContexts(); } } }, getGlareReInivteDelay(), TimeUnit.MILLISECONDS); return; } _inviteResponse = res; if (SIPHelper.isSuccessResponse(res)) { final byte[] content = SIPHelper.getRawContentWOException(res); if (content != null) { setRemoteSDP(content); } } if (_joinDelegate != null) { _joinDelegate.doInviteResponse(res, this, headers); } else if (_callDelegate != null) { _callDelegate.handleReinviteResponse(this, res, headers); } reInvitingRemote = false; } else if (SIPHelper.isCancel(res) || SIPHelper.isBye(res)) { // ignore the response } else if (SIPHelper.isPrack(res)) { if (SIPHelper.isSuccessResponse(res)) { final byte[] content = SIPHelper.getRawContentWOException(res); if (content != null) { setRemoteSDP(content); } } if (_joinDelegate != null) { _joinDelegate.doPrackResponse(res, this, headers); } } else if (SIPHelper.isUpdate(res)) { if (SIPHelper.isSuccessResponse(res)) { final byte[] content = SIPHelper.getRawContentWOException(res); if (content != null) { setRemoteSDP(content); } } if (_joinDelegate != null) { _joinDelegate.doUpdateResponse(res, this, headers); } if (_callDelegate != null) { _callDelegate.handleUpdateResponse(this, res, headers); } else { if(sendingUpdate) { sendingUpdate = false; LOG.debug("receive response for UPDATE:" + res); } else { LOG.warn("Call delegate is null, discarding UPDATE response:" + res); } } } else { final SipServletRequest req = (SipServletRequest) SIPHelper.getLinkSIPMessage(res.getRequest()); if (req != null) { LOG.debug("Found linked message, sending response to linked message:" + req); final SipServletResponse newRes = req.createResponse(res.getStatus(), res.getReasonPhrase()); SIPHelper.addHeaders(newRes, headers); SIPHelper.copyContent(res, newRes); newRes.send(); } else { LOG.warn("Discarding this response:" + res); } } } protected synchronized void setSIPCallState(final SIPCall.State state) { LOG.debug(this + " state changed from " + _cstate + " to " + state); _cstate = state; } protected synchronized boolean isNoAnswered() { return isNoAnswered(_cstate); } protected boolean isNoAnswered(final SIPCall.State state) { return state == SIPCall.State.INITIALIZED || state == SIPCall.State.INVITING || state == SIPCall.State.RINGING || state == SIPCall.State.ANSWERING || state == SIPCall.State.PROGRESSING || state == SIPCall.State.PROGRESSED; } protected synchronized boolean isAnswered() { return isAnswered(_cstate); } protected boolean isAnswered(final SIPCall.State state) { return state == SIPCall.State.ANSWERED; } protected synchronized boolean isTerminated() { return _cstate == SIPCall.State.FAILED || _cstate == SIPCall.State.DISCONNECTED || _cstate == SIPCall.State.REJECTED || _cstate == SIPCall.State.REDIRECTED; } protected void fail(Exception ex) { LOG.error(this + " failed.", ex); disconnect(true, CallCompleteEvent.Cause.ERROR, ex, null); } protected void doDisconnect(final boolean failed, final CallCompleteEvent.Cause cause, final Exception exception, Map<String, String> headers, SIPCall.State old) { if (LOG.isDebugEnabled()) { LOG.debug(this + " is disconnecting."); } terminate(cause, exception, null); try { if (isNoAnswered(old)) { try { if (this instanceof SIPOutgoingCall) { if (_invite != null && (_invite.getSession().getState() == SipSession.State.EARLY || _invite.getSession().getState() == SipSession.State.INITIAL)) { try { SipServletRequest cancelRequest = _invite.createCancel(); SIPHelper.addHeaders(cancelRequest, headers); cancelRequest.send(); } catch (Exception ex) { LOG.warn("Exception when disconnecting failed outbound call:" + ex.getMessage()); _invite.getSession().invalidate(); } } } else if (this instanceof SIPIncomingCall) { if (_invite != null && (_invite.getSession().getState() == SipSession.State.EARLY || _invite.getSession().getState() == SipSession.State.INITIAL)) { SipServletResponse declineResponse = _invite.createResponse(SipServletResponse.SC_DECLINE); SIPHelper.addHeaders(declineResponse, headers); declineResponse.send(); } } } catch (final Exception t) { LOG.warn("Exception when disconnecting call:" + t.getMessage()); } } else if (isAnswered(old) && _invite.getSession().getState() != SipSession.State.TERMINATED) { try { SipServletRequest byeReq = _signal.createRequest("BYE"); SIPHelper.addHeaders(byeReq, headers); byeReq.send(); } catch (final Exception t) { LOG.warn("Exception when disconnecting call:" + t.getMessage()); } } } finally { if (_invite != null) { SipApplicationSession appSession = _invite.getApplicationSession(); try { if (appSession.isReadyToInvalidate()) { appSession.invalidate(); if (LOG.isDebugEnabled()) { LOG.debug(appSession.getId() + " invalidated"); } } } catch (IllegalStateException doofus) { try { appSession.invalidate(); if (LOG.isDebugEnabled()) { LOG.debug(appSession.getId() + " invalidated anyway"); } } catch (Exception ex) { LOG.warn("Exception caught while invalidating SipApplicationSession " + appSession.getId(), ex); } } } } } protected void disconnect(final boolean failed, final CallCompleteEvent.Cause cause, final Exception exception, final Map<String, String> headers) { final SIPCall.State old = getSIPCallState(); synchronized (this) { if (isTerminated()) { if (LOG.isDebugEnabled()) { LOG.debug(this + " is already terminated."); } return; } if (failed) { this.setSIPCallState(SIPCall.State.FAILED); } else { this.setSIPCallState(SIPCall.State.DISCONNECTED); } notifyAll(); } doDisconnect(failed, cause, exception, headers, old); } protected void terminate(final CallCompleteEvent.Cause cause, final Exception exception, final Map<String, String> headers) { if (LOG.isDebugEnabled()) { LOG.debug(this + " is terminating."); } _context.removeCall(getId()); if (_service != null) { ((GenericMediaService) _service).release((cause == CallCompleteEvent.Cause.DISCONNECT || cause == CallCompleteEvent.Cause.NEAR_END_DISCONNECT || cause == CallCompleteEvent.Cause.CANCEL) ? true : false); _service = null; } destroyNetworkConnection(); Participant[] _joineesArray = _joinees.getJoinees(); for (final Participant participant : _joineesArray) { UnjoinCompleteEvent.Cause unjoinCause = UnjoinCompleteEvent.Cause.ERROR; if (cause == CallCompleteEvent.Cause.DISCONNECT || cause == CallCompleteEvent.Cause.NEAR_END_DISCONNECT) { unjoinCause = UnjoinCompleteEvent.Cause.DISCONNECT; } dispatch(new MohoUnjoinCompleteEvent(this, participant, unjoinCause, exception, true)); if (participant instanceof ParticipantContainer) { _context.getExecutor().execute(new InheritLogContextRunnable() { @Override public void run() { try { ((ParticipantContainer) participant).doUnjoin(SIPCallImpl.this, false); } catch (Exception e) { LOG.error("Exception when unjoining participant" + participant, e); } } }); } } _joinees.clear(); synchronized (_peers) { for (final Call peer : _peers) { try { _context.getExecutor().execute(new InheritLogContextRunnable() { @Override public void run() { peer.disconnect(); } }); } catch (final Throwable t) { LOG.warn("Exception when disconnecting peer:" + peer, t); } } _peers.clear(); } // TODO if (_joinDelegate != null) { if(!((_joinDelegate instanceof DirectAnswered2MultipleNOJoinDelegate || _joinDelegate instanceof DirectNI2MultipleNOJoinDelegate || _joinDelegate instanceof DirectNO2MultipleNOJoinDelegate) && _joinDelegate._call1 != this)) { if (cause == CallCompleteEvent.Cause.NEAR_END_DISCONNECT || cause == CallCompleteEvent.Cause.DISCONNECT) { _joinDelegate.done(JoinCompleteEvent.Cause.DISCONNECTED, exception); } else if (cause == CallCompleteEvent.Cause.CANCEL) { _joinDelegate.done(JoinCompleteEvent.Cause.DISCONNECTED, exception); } else { _joinDelegate.done(JoinCompleteEvent.Cause.ERROR, exception); } } _joinDelegate = null; } this.dispatch(new MohoCallCompleteEvent(this, cause, exception, headers)); _callDelegate = null; } @Override public void addParticipant(Participant p, JoinType type, Direction direction, Participant realJoined) { _joinees.add(p, type, direction, realJoined); } protected SipServletRequest getSipInitnalRequest() { return _invite; } protected SipServletResponse getLastReponse() { return _inviteResponse; } public byte[] getRemoteSdp() { return _remoteSDP; } public void setRemoteSDP(final byte[] sdp) { _remoteSDP = sdp; } public byte[] getLocalSDP() { return _localSDP; } public void setLocalSDP(final byte[] sdp) { _localSDP = sdp; } public void addPeer(final Call call, final JoinType type, final Direction direction) { if(type == JoinType.DIRECT && call instanceof SIPCallImpl) { setLocalSDP(((SIPCallImpl)call).getRemoteSdp()); } synchronized (_peers) { if (!_peers.contains(call)) { _peers.add(call); } } _joinees.add(call, type, direction); } protected void removePeer(final Call call) { synchronized (_peers) { if (_peers.contains(call)) { _peers.remove(call); } } _joinees.remove(call); } protected Call getLastPeer() { synchronized (_peers) { if (_peers.size() == 0) { return null; } return _peers.get(_peers.size() - 1); } } protected boolean isDirectlyJoined() { synchronized (_peers) { return isAnswered() && _network == null && _peers.size() > 0; } } protected boolean isBridgeJoined() { return (isAnswered() || _cstate == SIPCall.State.PROGRESSED) && _network != null; } protected synchronized void linkCall(final SIPCallImpl call, final JoinType type, final Direction direction) throws MsControlException { if (JoinType.isBridge(type)) { JoinDelegate.bridgeJoin(this, call, direction); } this.addPeer(call, type, direction); call.addPeer(this, type, JoinDelegate.reserve(direction)); } protected synchronized void unlinkDirectlyPeer() { if (isDirectlyJoined()) { synchronized (_peers) { for (final Call peer : _peers) { if (peer instanceof SIPCallImpl) { ((SIPCallImpl) peer).removePeer(this); } } _peers.clear(); } _joinees.clear(); } } protected synchronized void createNetworkConnection() throws MsControlException { if (_media == null) { final MsControlFactory mf = _context.getMSFactory(); _media = mf.createMediaSession(); // if (getSipSession() != null) { // if (LOG.isDebugEnabled()) { // LOG.debug("Set ms id with call id :" + getSipSession().getCallId()); // } // // final Parameters params = _media.createParameters(); // // params.put(MediaObject.MEDIAOBJECT_ID, "MS-" + // getSipSession().getCallId()); // _media.setParameters(params); // } } if (_network == null) { Parameters params = _media.createParameters(); _network = _media.createNetworkConnection(NetworkConnection.BASIC, params); _network.getSdpPortManager().addListener(this); } } protected synchronized void destroyNetworkConnection() { this.destroyNetworkConnection(true); } protected synchronized void destroyNetworkConnection(boolean disconnectedCall) { if (LOG.isDebugEnabled()) { LOG.debug(this + "destroying NetworkConnection" + disconnectedCall); } if (_network != null) { try { MediaDialect dialect = ((ApplicationContextImpl) this.getApplicationContext()).getDialect(); if (dialect != null) { dialect.stopCallRecord(_network); } } catch (final Throwable t) { LOG.warn("Exception when stopping call record", t); } } if (_service != null) { try { ((GenericMediaService) _service).release(disconnectedCall); } catch (final Throwable t) { LOG.warn("Exception when releasing media service", t); } _service = null; } if (_network != null) { try { _network.release(); } catch (final Throwable t) { LOG.warn("Exception when releasing networkconnection", t); } _network = null; } if (_multiplejoiningMixer != null) { try { destroyMultipleJoiningMixer(); } catch (final Throwable t) { LOG.warn("Exception when releasing multiplejoiningMixer", t); } _multiplejoiningMixer = null; } if (_media != null) { try { _media.release(); } catch (final Throwable t) { LOG.warn("Exception when releasing media object", t); } _media = null; } } protected synchronized void processSDPOffer(final SipServletMessage msg) throws MediaException { try { if (_network == null) { createNetworkConnection(); } final byte[] sdpOffer = msg == null ? null : msg.getRawContent(); if (sdpOffer == null) { _network.getSdpPortManager().generateSdpOffer(); } else { _network.getSdpPortManager().processSdpOffer(sdpOffer); } } catch (final Throwable t) { LOG.error(t); if (msg instanceof SipServletRequest) { try { ((SipServletRequest) msg).createResponse(SipServletResponse.SC_SERVER_INTERNAL_ERROR).send(); } catch (final IOException e1) { LOG.warn("Exception when sending error response ", e1); } } throw new MediaException(t); } } protected synchronized void processSDPAnswer(final SipServletMessage msg) throws MediaException { if (_network == null) { throw new MediaException("NetworkConnection is NULL"); } try { final byte[] remoteSdp = msg.getRawContent(); if (remoteSdp != null) { _network.getSdpPortManager().processSdpAnswer(remoteSdp); } return; } catch (final Throwable t) { throw new MediaException(t); } } public synchronized void startJoin(final Participant participant, final JoinDelegate delegate) { if (_joinDelegate != null) { throw new IllegalStateException("other join operation in process."); } _operationInProcess = true; _joinDelegate = delegate; } public JoinDelegate getJoinDelegate() { return _joinDelegate; } @Override public JoinDelegate getJoinDelegate(String participantID) { return _joinDelegate; } protected void setCallDelegate(final SIPCallDelegate delegate) { _callDelegate = delegate; } protected Joint doJoin(final SIPCallImpl other, final JoinType type, final boolean force, final Direction direction, boolean dtmfPassThrough, SettableJointImpl joint) throws Exception { if (joint == null) { joint = new SettableJointImpl(); } // join strategy check on either side final ExecutionException e = JoinDelegate.checkJoinStrategy(this, other, type, force); if (e == null) { _joinDelegate = createJoinDelegate(other, type, direction); _joinDelegate.setSettableJoint(joint); _joinDelegate.setDtmfPassThrough(dtmfPassThrough); other.startJoin(this, _joinDelegate); _joinDelegate.doJoin(); } else { // dispatch BUSY event JoinCompleteEvent joinCompleteEvent = new MohoJoinCompleteEvent(this, other, Cause.BUSY, e, true); dispatch(joinCompleteEvent); joint.done(e); } return joint; } // RMI based. // protected Joint doJoin(final RemoteParticipant other, final JoinType type, // final Direction direction) // throws Exception { // JoinDelegate joinDelegate = null; // if (type != JoinType.DIRECT) { // joinDelegate = new LocalRemoteJoinDelegate(this, other, direction); // } // else { // joinDelegate = new DirectLocalRemoteJoinDelegate(this, other, direction); // } // // SettableJointImpl joint = new SettableJointImpl(); // joinDelegate.setSettableJoint(joint); // // joinDelegate.doJoin(); // // return joint; // } protected Joint doJoin(final RemoteParticipant other, final JoinType type, boolean force, final Direction direction, boolean dtmfPassThrough, SettableJointImpl joint) throws Exception { // 1 create outgoing call. // 2 join the outgoing call and return joint. String[] parsedJoinerID = ParticipantIDParser.parseEncodedId(this.getId()); String[] parsedJoineeID = ParticipantIDParser.parseEncodedId(other.getId()); SIPEndpoint joinerEndpoint = (SIPEndpoint) this.getApplicationContext().createEndpoint( "sip:" + this.getId() + "@" + parsedJoinerID[0]); SIPEndpoint joineeEndpoint = (SIPEndpoint) this.getApplicationContext().createEndpoint( "sip:" + other.getId() + "@" + parsedJoineeID[0]); RemoteJoinOutgoingCall outgoingCall = new RemoteJoinOutgoingCall((ExecutionContext) this.getApplicationContext(), joinerEndpoint, joineeEndpoint, null); outgoingCall.setX_Join_Direction(direction); outgoingCall.setX_Join_Force(force); outgoingCall.setX_Join_Type(type); LOG.debug("Starting remotejoin. joiner:" + this + ". joinee:" + other.getId() + ". created RemoteJoinOutgoingCall:" + outgoingCall); _operationInProcess = false; return this.join(outgoingCall, type, force, direction, dtmfPassThrough, joint); } protected Joint doJoin(final Participant other, final JoinType type, final boolean force, final Direction direction, boolean dtmfPassThrough, SettableJointImpl joint) throws Exception { if (!(other.getMediaObject() instanceof Joinable)) { throw new IllegalArgumentException("MediaObject is't joinable."); } if (joint == null) { joint = new SettableJointImpl(); } // join strategy check on either side final ExecutionException e = JoinDelegate.checkJoinStrategy(this, other, type, force); if (e == null) { _joinDelegate = new OtherParticipantJoinDelegate(this, other, type, direction); _joinDelegate.setSettableJoint(joint); _joinDelegate.setDtmfPassThrough(dtmfPassThrough); _joinDelegate.doJoin(); } else { // dispatch BUSY event JoinCompleteEvent joinCompleteEvent = new MohoJoinCompleteEvent(this, other, Cause.BUSY, e, true); dispatch(joinCompleteEvent); JoinCompleteEvent peerJoinCompleteEvent = new MohoJoinCompleteEvent(other, this, Cause.BUSY, e, false); other.dispatch(peerJoinCompleteEvent); joint.done(e); } return joint; } protected abstract JoinDelegate createJoinDelegate(final Direction direction); protected abstract JoinDelegate createJoinDelegate(final SIPCallImpl other, final JoinType type, final Direction direction); protected JoinDelegate createJoinDelegate(final Call[] others, final JoinType type, final Direction direction) { JoinDelegate retval = null; List<SIPCallImpl> candidates = new LinkedList<SIPCallImpl>(); for (Call call : others) { candidates.add((SIPCallImpl) call); } if (type == JoinType.DIRECT) { if (this.isAnswered()) { retval = new DirectAnswered2MultipleNOJoinDelegate(type, direction, this, Utils.suppressEarlyMedia(getApplicationContext()), candidates); } } else { retval = new MultipleNOBridgeJoinDelegate(type, direction, this, candidates); } return retval; } protected boolean isOperationInprocess() { return _operationInProcess; } protected void setOperationInprocess(final boolean flag) { _operationInProcess = flag; } protected enum HoldState { None, Holding, Held, UnHolding, Muting, Muted, UnMuting, Deafing, Deafed, Undeafing } protected HoldState _holdState = HoldState.None; protected HoldState _muteState = HoldState.None; protected HoldState _deafState = HoldState.None; protected int waitRespNum; protected synchronized HoldState getMuteState() { return _muteState; } protected synchronized void setMuteState(final HoldState muteState) { _muteState = muteState; } protected synchronized HoldState getHoldState() { return _holdState; } protected synchronized void setHoldState(final HoldState holdState) { _holdState = holdState; if (_holdState == HoldState.Holding || _holdState == HoldState.UnHolding) { waitRespNum = 2; } } protected synchronized void setDeafState(final HoldState deafState) { _deafState = deafState; } protected synchronized HoldState getDeafState() { return _deafState; } protected synchronized boolean isHoldingProcess() { return _holdState == HoldState.Holding || _holdState == HoldState.UnHolding; } protected synchronized boolean isMutingProcess() { return _muteState == HoldState.Muting || _muteState == HoldState.UnMuting; } protected synchronized boolean isDeafingProcess() { return _deafState == HoldState.Deafing || _deafState == HoldState.Undeafing; } protected synchronized void holdResp() { waitRespNum--; if (waitRespNum == 0) { if (getHoldState() == HoldState.Holding) { setHoldState(HoldState.Held); } else if (getHoldState() == HoldState.UnHolding) { setHoldState(HoldState.None); } this.notify(); } } /** * send a sendonly SDP and stop to send media data to this endpoint */ @Override public synchronized void hold() { hold(false); } /** * send a sendonly SDP and stop to send media data to this endpoint */ public synchronized void hold(final boolean send) { if (this.getSIPCallState() != SIPCall.State.ANSWERED) { throw new IllegalStateException("call have not been answered"); } if (_holdState == HoldState.Held || _holdState == HoldState.Holding) { return; } if (_operationInProcess) { throw new IllegalStateException("other operation in process."); } _operationInProcess = true; try { setHoldState(HoldState.Holding); _callDelegate.hold(this, send); while (getHoldState() != HoldState.Held && getHoldState() != HoldState.None && isAnswered()) { try { this.wait(); } catch (final InterruptedException e) { LOG.warn("InterruptedException when wait hold, the HoldState " + getHoldState()); } } } catch (final MsControlException e) { setHoldState(HoldState.None); throw new MediaException("exception when holding", e); } catch (final IOException e) { setHoldState(HoldState.None); throw new SignalException("exception when holding", e); } catch (final SdpException e) { setHoldState(HoldState.None); throw new SignalException("exception when holding", e); } catch (final Throwable t) { setHoldState(HoldState.None); LOG.error("Error when holding", t); } finally { _operationInProcess = false; reInvitingRemote = false; } } @Override public boolean isHold() { return _holdState == HoldState.Held; } @Override public boolean isMute() { return _muteState == HoldState.Muted; } /** * send a sendonly SDP to the endpoint, but still send media data to this * endpoint */ @Override public synchronized void mute() { if (this.getSIPCallState() != SIPCall.State.ANSWERED) { throw new IllegalStateException("call have not been answered"); } if (_muteState == HoldState.Muted || _muteState == HoldState.Muting) { return; } if (_operationInProcess) { throw new IllegalStateException("other operation in process."); } _operationInProcess = true; try { setMuteState(HoldState.Muting); _callDelegate.mute(this); while (getMuteState() != HoldState.Muted && getMuteState() != HoldState.None&& isAnswered()) { try { this.wait(); } catch (final InterruptedException e) { LOG.warn("InterruptedException when wait mute, the MuteState " + getMuteState()); } } } catch (final IOException e) { setMuteState(HoldState.None); throw new SignalException("exception when muting", e); } catch (final SdpException e) { setMuteState(HoldState.None); throw new SignalException("exception when muting", e); } catch (final Throwable t) { setHoldState(HoldState.None); LOG.error("Error when mute", t); } finally { _operationInProcess = false; reInvitingRemote = false; } } @Override public synchronized void unhold() { if (_holdState != HoldState.Held) { return; } if (_operationInProcess) { throw new IllegalStateException("other operation in process."); } _operationInProcess = true; HoldState oldHoldState = null; try { oldHoldState = getHoldState(); setHoldState(HoldState.UnHolding); _callDelegate.unhold(this); while (getHoldState() != HoldState.None && isAnswered()) { try { this.wait(); } catch (final InterruptedException e) { LOG.warn("InterruptedException when wait unhold, the HoldState " + getHoldState()); } } } catch (final MsControlException e) { setHoldState(oldHoldState); throw new SignalException("exception when unholding", e); } catch (final IOException e) { setHoldState(oldHoldState); throw new SignalException("exception when unholding", e); } catch (final SdpException e) { setHoldState(oldHoldState); throw new SignalException("exception when unholding", e); } catch (final Throwable t) { setHoldState(HoldState.None); LOG.error("Error when unhold", t); } finally { _operationInProcess = false; reInvitingRemote = false; } } @Override public synchronized void unmute() { if (_muteState != HoldState.Muted) { return; } if (_operationInProcess) { throw new IllegalStateException("other operation in process."); } _operationInProcess = true; HoldState oldMuteState = null; try { oldMuteState = getMuteState(); setMuteState(HoldState.UnMuting); _callDelegate.unmute(this); while (getMuteState() != HoldState.None && isAnswered()) { try { this.wait(); } catch (final InterruptedException e) { LOG.warn("InterruptedException when wait unmute, the MuteState " + getMuteState()); } } } catch (final IOException e) { setMuteState(oldMuteState); throw new SignalException("exception when unmuting", e); } catch (final SdpException e) { setMuteState(oldMuteState); throw new SignalException("exception when unmuting", e); } catch (final Throwable t) { setHoldState(HoldState.None); LOG.error("Error when unmute", t); } finally { _operationInProcess = false; reInvitingRemote = false; } } // for invite event ============= @Override public SipServletRequest getSipRequest() { return _invite; } @Override public String getHeader(final String name) { return _invite.getHeader(name); } @Override public ListIterator<String> getHeaders(final String name) { return _invite.getHeaders(name); } @Override public Iterator<String> getHeaderNames() { return _invite.getHeaderNames(); } @Override public Endpoint getInvitor() { return _caller; } @Override public CallableEndpoint getInvitee() { return _callee; } // for dispatchable eventsource over========= public JoinData getJoiningPeer() { return _bridgeJoiningPeer; } public void setJoiningPeer(final JoinData bridgeJoiningPeer) { _bridgeJoiningPeer = bridgeJoiningPeer; } @Override public synchronized Endpoint getAddress() { return _address; } @Override public String getRemoteAddress() { return _id; } public void createMultipleJoiningMixer() throws MsControlException { if (_multiplejoiningMixer == null) { Parameters params = Parameters.NO_PARAMETER; params = _network.getParameters(new Parameter[] {MediaObject.MEDIAOBJECT_ID}); params.put(MediaObject.MEDIAOBJECT_ID, "JoinStrategy-ShadowMixer-" + params.get(MediaObject.MEDIAOBJECT_ID)); _multiplejoiningMixer = _media.createMediaMixer(MediaMixer.AUDIO, params); } } public void destroyMultipleJoiningMixer() throws MsControlException { try { if (_multiplejoiningMixer != null) { if (LOG.isDebugEnabled()) { LOG.debug("destroyMultipleJoiningMixer: " + _multiplejoiningMixer); } if (_network != null) { _network.unjoin(_multiplejoiningMixer); } _multiplejoiningMixer.release(); } } finally { _multiplejoiningMixer = null; } } public MediaMixer getMultipleJoiningMixer() { return _multiplejoiningMixer; } class JoinRequest { private Participant peer; private Call[] calls; private JoinType type; private Direction direction; private boolean force; private boolean dtmfPassThrough; private SettableJointImpl joint; public JoinRequest(Participant peer, JoinType type, Direction direction, boolean force, boolean dtmfPassThrough, SettableJointImpl joint) { super(); this.peer = peer; this.direction = direction; this.type = type; this.dtmfPassThrough = dtmfPassThrough; this.joint = joint; this.force = force; } public JoinRequest(Call[] calls, JoinType type, Direction direction, boolean force, boolean dtmfPassThrough, SettableJointImpl joint) { super(); this.calls = calls; this.direction = direction; this.type = type; this.dtmfPassThrough = dtmfPassThrough; this.joint = joint; this.force = force; } public JoinRequest(Direction direction, SettableJointImpl joint) { super(); this.direction = direction; this.joint = joint; } public SettableJointImpl getJoint() { return joint; } public Participant getPeer() { return peer; } public Direction getDirection() { return direction; } public JoinType getType() { return type; } public boolean isDtmfPassThrough() { return dtmfPassThrough; } public boolean isForce() { return force; } public Call[] getCalls() { return calls; } } private boolean sendingUpdate; public boolean isSendingUpdate() { return sendingUpdate; } public void setSendingUpdate(boolean sendingUpdate) { this.sendingUpdate = sendingUpdate; } public void update() { if (_cstate != SIPCall.State.PROGRESSED && _cstate != SIPCall.State.ANSWERED) { LOG.warn("Call media didn't negotiate, can't send UPDATE"); return; } try { SipServletRequest updateReq = _invite.getSession().createRequest("UPDATE"); if (_localSDP != null) { updateReq.setContent(SDPUtils.formulateSDP(this, _localSDP), "application/sdp"); } sendingUpdate = true; updateReq.send(); } catch (Exception ex) { sendingUpdate = false; LOG.error("Can't send UPDATE reqeust.", ex); } } public SessionDescription createSendonlySDP(final byte[] sdpByte) throws UnsupportedEncodingException, SdpException { SdpFactory sdpFactory = ((ExecutionContext) getApplicationContext()).getSdpFactory(); SessionDescription sd = sdpFactory.createSessionDescription(new String(sdpByte, "iso8859-1")); sd.removeAttribute("sendrecv"); sd.removeAttribute("recvonly"); MediaDescription md = ((MediaDescription) sd.getMediaDescriptions(false).get(0)); md.removeAttribute("sendrecv"); md.removeAttribute("recvonly"); md.setAttribute("sendonly", null); return sd; } public synchronized void reInviteRemote(Object sdp, Map<String, String> headers, Map<String, String> attributes) throws IOException { if(!isAnswered()) { throw new IllegalStateException(this + " was terminated."); } while(pendingReinvite && isAnswered()) { try { needReInivteRemote = true; notifyAll(); wait(); } catch (InterruptedException e) { //ignore } } needReInivteRemote = false; SipServletRequest reInvite = getSipSession().createRequest("INVITE"); if(sdp != null) { reInvite.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); } if(headers != null) { reInvite.setAttribute(Att_REINVITE_HEADERS, headers); SIPHelper.addHeaders(reInvite, headers); } if(attributes != null) { reInvite.setAttribute(Att_REINVITE_ATTRIBUTES, attributes); for(Entry<String, String> entry: attributes.entrySet()) { reInvite.setAttribute(entry.getKey(), entry.getValue()); } } if(this instanceof OutgoingCall && _invite.getHeader("ALLOW") != null) { reInvite.addHeader("ALLOW", _invite.getHeader("ALLOW")); } reInvite.send(); reInvitingRemote = true; } private synchronized void waitProcessReInvite() { while(processingReinvite && isAnswered()) { try { LOG.debug(this +" processing re-INVITE. wait it complete."); wait(); } catch (InterruptedException e) { //ignore } if(!isAnswered()) { String err = this + " is already terminated. operation can't complete."; LOG.warn(err); throw new IllegalStateException(err); } } } public Origin getPreviousOrigin() { return previousOrigin; } public void setPreviousOrigin(Origin previousOrigin) { this.previousOrigin = previousOrigin; } }