package com.voxeo.tropo;
import java.io.IOException;
import javax.annotation.Resource;
import javax.management.MBeanServer;
import javax.script.ScriptException;
import javax.sdp.SessionDescription;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.sip.Address;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServlet;
import javax.servlet.sip.SipServletMessage;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TelURL;
import javax.servlet.sip.URI;
import org.apache.log4j.Logger;
import com.voxeo.tropo.app.AbstractApplicationManager;
import com.voxeo.tropo.app.Application;
import com.voxeo.tropo.app.ApplicationInstance;
import com.voxeo.tropo.app.ApplicationManager;
import com.voxeo.tropo.app.InvalidApplicationException;
import com.voxeo.tropo.app.RedirectException;
import com.voxeo.tropo.core.Call;
import com.voxeo.tropo.core.CallImpl;
import com.voxeo.tropo.core.IncomingCall;
import com.voxeo.tropo.core.OutgoingCall;
import com.voxeo.tropo.core.Call.State;
import com.voxeo.tropo.util.DumpHelper;
import com.voxeo.tropo.util.Utils;
@SuppressWarnings("serial")
@javax.servlet.sip.annotation.SipServlet(name = "tropo", loadOnStartup = 1)
public class SIPDriver extends SipServlet {
private static final Logger LOG = Logger.getLogger(SIPDriver.class);
protected ApplicationManager _appMgr;
@Resource
protected SipFactory _sipFactory;
@Resource
protected MBeanServer _server;
@Override
public void init() {
final ServletContext ctx = getServletContext();
try {
System.setProperty(ServletContextConstants.ROOT_PATH, ctx.getRealPath("/"));
Configuration.init(this.getServletConfig());
AbstractApplicationManager.load(Configuration.get(), ctx);
DumpHelper.initialization(_server);
}
catch (final Exception e) {
LOG.error("Unable to initialize Tropo:", e);
throw new RuntimeException(e);
}
_appMgr = (ApplicationManager) ctx.getAttribute(ServletContextConstants.APP_MANAGER);
}
@Override
public void destroy() {
_appMgr.dispose();
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
try {
super.service(req, res);
}
finally {
Utils.clearLogContext();
}
}
@Override
protected void doInvite(final SipServletRequest req) throws ServletException, IOException {
if (req.isInitial()) {
req.createResponse(SipServletResponse.SC_TRYING).send();
LOG.info("A new call is coming from " + req.getFrom().getURI() + " to " + req.getTo().getURI());
final URI token = req.getTo().getURI();
try {
// set guid session id for logging
req.getSession().getApplicationSession().setAttribute(ServletContextConstants.GUID_SESSION_ID, Utils.getGUID());
final Application app = _appMgr.get(token);
Utils.setLogContext(app, req);
req.createResponse(SipServletResponse.SC_RINGING).send();
app.execute(req);
}
catch (final RedirectException e) {
LOG.info("Redirect request for " + req.getTo());
SipServletResponse response = req.createResponse(SipServletResponse.SC_MOVED_TEMPORARILY);
for(SipURI uri : e.getContacts()) {
response.addHeader("Contact", uri.toString());
}
response.send();
}
catch (final InvalidApplicationException e) {
LOG.error("Unknown application name for " + req.getTo() + ". " + e.getMessage(), e);
req.createResponse(SipServletResponse.SC_NOT_FOUND).send();
}
catch (final ScriptException e) {
LOG.error("Invalid script for " + req.getTo() + ". " + e.getMessage(), e);
req.createResponse(SipServletResponse.SC_TEMPORARLY_UNAVAILABLE).send();
}
}
else { // re-invite
final CallImpl call = findCall(req);
if (call != null) {
if (call.getState() == Call.State.ANSWERED) {
updateEndpoint(call, req);
}
}
}
}
@Override
protected void doAck(final SipServletRequest req) throws ServletException, IOException {
final CallImpl call = findCall(req);
if (call != null) {
call.lock();
try {
if (call.getState() == Call.State.ANSWERING) {
updateEndpoint(call, req);
call.setState(Call.State.ANSWERED);
}
else if (call.getState() == Call.State.REJECTING) {
call.setState(Call.State.REJECTED);
}
else if (call.getState() == Call.State.REDIRECTING) {
call.setState(Call.State.REDIRECTED);
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("No match state: " + call);
}
}
}
finally {
call.unlock();
}
}
}
@Override
protected void doCancel(final SipServletRequest req) throws ServletException, IOException {
final CallImpl call = findCall(req);
if (call != null) {
call.lock();
try {
if (call.getState() == State.RINGING) {
call.setState(Call.State.DISCONNECTED);
}
}
finally {
call.unlock();
}
}
}
@Override
protected void doBye(final SipServletRequest req) throws ServletException, IOException {
final SipApplicationSession as = req.getSession().getApplicationSession();
final ApplicationInstance inst = (ApplicationInstance) as.getAttribute(ApplicationInstance.INST);
final CallImpl call = findCall(req);
if (call != null) {
call.setState(Call.State.DISCONNECTED);
LOG.info("A call just ended: " + req.getFrom().getURI() + "--->" + req.getTo().getURI());
req.createResponse(SipServletResponse.SC_OK).send();
}
else {
req.createResponse(SipServletResponse.SC_DOES_NOT_EXIT_ANYWHERE).send();
}
// check which leg is this???
if (inst != null) {
inst.terminate();
}
}
@Override
protected void doProvisionalResponse(final SipServletResponse res) throws ServletException, IOException {
final CallImpl call = findCall(res);
if (call == null || call instanceof IncomingCall) {
return;
}
if (res.getMethod().equalsIgnoreCase("INVITE")) {
final int code = res.getStatus();
switch (code) {
case SipServletResponse.SC_RINGING:
call.lock();
try {
if (call.getState() == Call.State.ANSWERING) {
call.setState(Call.State.RINGING);
}
}
finally {
call.unlock();
}
break;
case SipServletResponse.SC_SESSION_PROGRESS:
final OutgoingCall out = (OutgoingCall) call;
if (out.isAnswerOnMedia()) {
final Object o = res.getContent();
if (o instanceof SessionDescription) {
call.lock();
try {
final Call.State state = call.getState();
if (state == Call.State.ANSWERING || state == Call.State.RINGING) {
call.setState(Call.State.ANSWERED);
}
}
finally {
call.unlock();
}
}
}
break;
default:
break;
}
}
}
@Override
protected void doSuccessResponse(final SipServletResponse res) throws ServletException, IOException {
if (res.getMethod().equalsIgnoreCase("INVITE")) {
final CallImpl call = findCall(res);
if (call != null) {
call.lock();
try {
final Call.State state = call.getState();
if (state == Call.State.ANSWERING || state == Call.State.RINGING) {
updateEndpoint(call, res);
call.setState(Call.State.ANSWERED);
}
}
finally {
call.unlock();
}
}
res.createAck().send();
}
else if (res.getMethod().equalsIgnoreCase("BYE")) {
// do we do anything?
}
}
@Override
protected void doErrorResponse(final SipServletResponse res) throws ServletException, IOException {
if (res.getMethod().equalsIgnoreCase("INVITE")) {
final CallImpl call = findCall(res);
if (call != null) {
call.lock();
try {
final Call.State state = call.getState();
if (state == Call.State.ANSWERING || state == Call.State.RINGING) {
call.setState(Call.State.DISCONNECTED);
}
else if (state == Call.State.ANSWERED) { // answerOnMedia case
call.hangup();
}
}
finally {
call.unlock();
}
}
}
}
@Override
protected void doRedirectResponse(final SipServletResponse res) throws ServletException, IOException {
if (res.getMethod().equalsIgnoreCase("INVITE")) {
final CallImpl call = findCall(res);
if (call != null) {
call.lock();
try {
final Call.State state = call.getState();
if (state == Call.State.ANSWERING || state == Call.State.RINGING) {
final Address contact = res.getAddressHeader("Contact");
if (contact != null) {
URI target = contact.getURI();
final SipServletRequest req = _sipFactory.createRequest(res.getApplicationSession(), "INVITE", res
.getFrom(), res.getTo());
if (target instanceof TelURL) {
target = _sipFactory.createURI(Utils.prefixNumber(String.valueOf(target)));
}
req.setRequestURI(target);
((OutgoingCall) call).update(req);
req.send();
res.getSession().invalidate();
}
}
}
finally {
call.unlock();
}
}
}
}
protected CallImpl findCall(final SipServletMessage message) {
Utils.setLogContext(message);
final CallImpl call = (CallImpl) message.getSession().getAttribute(CallImpl.INST);
if (call == null) {
LOG.warn("No call is found for " + message);
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("do" + (message instanceof SipServletResponse ? ((SipServletResponse) message).getStatus() : "")
+ message.getMethod() + " find call: " + call);
}
}
return call;
}
protected void updateEndpoint(final CallImpl call, final SipServletMessage message) {
Object content = null;
try {
content = message.getContent();
}
catch (final Throwable t) {
LOG.warn("getContent error " + t);
}
if (content != null) {
call.updateEndpoint(message.getRemoteAddr(), message.getRemotePort(), content.toString());
}
}
}