/** * Copyright 2010 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.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import javax.media.mscontrol.join.Joinable.Direction; import javax.servlet.sip.SipApplicationSession; import javax.servlet.sip.SipServletRequest; import org.apache.log4j.Logger; import com.voxeo.moho.ApplicationContextImpl; import com.voxeo.moho.Call; import com.voxeo.moho.OutgoingCall; import com.voxeo.moho.SignalException; import com.voxeo.moho.common.util.Utils; import com.voxeo.moho.spi.ExecutionContext; import com.voxeo.moho.util.SDPUtils; import com.voxeo.moho.util.SessionUtils; public class SIPOutgoingCall extends SIPCallImpl implements OutgoingCall { private static final Logger LOG = Logger.getLogger(SIPCallImpl.class); protected SIPEndpoint _from; protected SipApplicationSession _appSession; protected Map<String, String> _headers; protected SIPCall _continueRoutingOrigCall; protected SIPOutgoingCall(final ExecutionContext context, final SIPEndpoint from, final SIPEndpoint to, final Map<String, String> headers) { this(context, from, to, headers, null); } protected SIPOutgoingCall(final ExecutionContext context, final SIPEndpoint from, final SIPEndpoint to, final Map<String, String> headers, SIPCall originalCall) { super(context); if (from == null || to == null) { throw new IllegalArgumentException("to or from can't be null"); } _address = to; _from = from; _headers = headers; if(originalCall != null){ setContinueRouting(originalCall); } createRequest(); } @Override protected JoinDelegate createJoinDelegate(final Direction direction) { JoinDelegate retval = null; if (isNoAnswered()) { retval = new Media2NOJoinDelegate(this); } else if (isAnswered()) { retval = new Media2AOJoinDelegate(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 DirectNO2NOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectNI2NOJoinDelegate((SIPIncomingCall) other, this, direction, (SIPIncomingCall) other); } } else if (other.isAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectNO2AOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectAI2NOJoinDelegate((SIPIncomingCall) other, this, direction, (SIPIncomingCall) other); } } } else if (isAnswered()) { if (other.isNoAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectNO2AOJoinDelegate((SIPOutgoingCall) other, this, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectNI2AOJoinDelegate((SIPIncomingCall) other, this, direction, (SIPIncomingCall) other); } } else if (other.isAnswered()) { if (other instanceof SIPOutgoingCall) { retval = new DirectAO2AOJoinDelegate(this, (SIPOutgoingCall) other, direction, (SIPOutgoingCall) other); } else if (other instanceof SIPIncomingCall) { retval = new DirectAI2AOJoinDelegate((SIPIncomingCall) other, this, 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 DirectNO2MultipleNOJoinDelegate(type, direction, this, Utils.suppressEarlyMedia(getApplicationContext()), candidates); return retval; } else { return super.createJoinDelegate(others, type, direction); } } protected synchronized void call(final byte[] sdp) throws IOException { if (isNoAnswered()) { if (_invite == null) { createRequest(); } if (sdp != null) { _invite.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); } setSIPCallState(SIPCall.State.INVITING); _invite.send(); } else if (isAnswered()) { reInviteRemote(sdp, null, null); } } /** * create INVITE request and send * * @param sdp * sdp used in INVITE request. * @param appSession * applicationSession that used to create INVITE request. * @throws IOException */ protected synchronized void call(final byte[] sdp, final SipApplicationSession appSession) throws IOException { if (_appSession == null) { _appSession = appSession; } call(sdp); } /** * create INVITE request and send * * @param sdp * sdp used in INVITE request. * @param appSession * applicationSession that used to create INVITE request. * @throws IOException */ protected synchronized void call(final byte[] sdp, final SipApplicationSession appSession, final String replacesHeader) throws IOException { if (_appSession == null) { _appSession = appSession; } if (replacesHeader != null) { if (_headers == null) { _headers = new HashMap<String, String>(); } _headers.put("Replaces", replacesHeader); } call(sdp); } protected void createRequest() { try { _invite = SIPHelper.createSipInitnalRequest(_context.getSipFactory(), "INVITE", _from.getSipAddress(), _address.getSipAddress(), _headers, _appSession, _continueRoutingOrigCall!= null ? _continueRoutingOrigCall.getSipRequest() : null, this.getApplicationContext()); _signal = _invite.getSession(); if (_appSession == null) { _appSession = _signal.getApplicationSession(); } SessionUtils.setEventSource(_signal, this); _signal.setHandler(((ApplicationContextImpl) getApplicationContext()).getSIPController().getServletName()); } catch (final Exception e) { if(_invite != null && _invite.getSession() != null){ try{ _invite.getSession().invalidate(); } catch(Exception ex){ LOG.error("Exception when invalidating session:"+ _invite); } _invite = null; } ((ExecutionContext)getApplicationContext()).removeCall(this.getId()); throw new SignalException(e); } } @Override public byte[] getJoinSDP() throws IOException { this.call(null); return null; } @Override public void processSDPAnswer(byte[] sdp) throws IOException { if (_inviteResponse != null) { SipServletRequest ack = _inviteResponse.createAck(); ack.setContent(SDPUtils.formulateSDP(this, sdp), "application/sdp"); ack.send(); } else { throw new IllegalStateException(""); } } @Override public byte[] processSDPOffer(byte[] sdp) throws IOException { this.call(sdp); return null; } public void setContinueRouting(final SIPCall origCall) { _continueRoutingOrigCall = origCall; } protected int getGlareReInivteDelay() { Random random = new Random(); int delay = (random.nextInt(191) + 210) * 10; return delay; } }