package com.voxeo.tropo.app; import java.io.IOException; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.script.ScriptException; import javax.servlet.http.HttpServletRequest; import javax.servlet.sip.SipApplicationSession; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipURI; import javax.servlet.sip.annotation.SipListener; import org.apache.log4j.Logger; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.transport.TTransport; import com.voxeo.tropo.ErrorException; import com.voxeo.tropo.FatalException; import com.voxeo.tropo.core.Call; import com.voxeo.tropo.core.CallImpl; import com.voxeo.tropo.core.IncomingCall; import com.voxeo.tropo.core.SimpleCallFactory; import com.voxeo.tropo.core.SimpleIncomingCall; import com.voxeo.tropo.thrift.AlertStruct; import com.voxeo.tropo.thrift.AuthenticationException; import com.voxeo.tropo.thrift.BindException; import com.voxeo.tropo.thrift.HangupStruct; import com.voxeo.tropo.thrift.Notifier; import com.voxeo.tropo.thrift.PromptStruct; import com.voxeo.tropo.thrift.SystemException; import com.voxeo.tropo.thrift.TransferStruct; import com.voxeo.tropo.thrift.TropoException; import com.voxeo.tropo.transport.reversetcp.TSocketFactory; import com.voxeo.tropo.util.Utils; @SipListener public class ThriftApplication extends AbstractApplication implements RemoteApplication, Serializable { private static final Logger LOG = Logger.getLogger(ThriftApplication.class); private static final long serialVersionUID = 6698570731791945838L; public static final String CALL_FACTORY = "com.voxeo.tropo.thrift.callFactory"; protected List<SipURI> _contacts; protected String _key; protected transient Notifier.Client _notifier; protected transient TTransport _transport; protected transient Map<String, Call> _calls; protected transient String _token; protected transient int _beats; public ThriftApplication(final RemoteApplicationManager mgr, final ThriftURL url, final int aid, final String appId, final List<SipURI> contacts) { super(mgr, url, "Thrift", aid, appId, null); _contacts = contacts; _calls = new ConcurrentHashMap<String, Call>(); _key = Utils.getGUID(); setLogContext(null); } public ThriftApplication(final ThriftApplication shared, final String appId) throws AuthenticationException, SystemException, TException { super(shared.getManager(), shared.getURL(), "Thrift", shared.getAccountID(), appId, null); _contacts = shared.getContacts(); _calls = new ConcurrentHashMap<String, Call>(); _key = Utils.getGUID(); _notifier = shared.getNotifier(); setLogContext(null); } public synchronized void dispose() { if (_calls != null) { for(Call call : _calls.values()) { call.hangup(); if (_notifier != null) { try { _notifier.hangup(_token, ((CallImpl)call).getId(), new HangupStruct()); } catch(Exception e) { //ignore } } } _calls = null; } if (_transport != null && _notifier != null) { try { _notifier.unbind(_token); } catch(Throwable t) { //ignore } _notifier = null; _transport.close(); } } protected synchronized Notifier.Client getNotifier() throws AuthenticationException, SystemException, TException { if (_notifier == null) { //_transport = new TSocket(_url.getHost(), _url.getPort()); _transport = TSocketFactory.getInstance(((ThriftAppMgr) getManager())._serverPort + 1).getTransport(getApplicationKey()); TBinaryProtocol binaryProtocol = new TBinaryProtocol(_transport); Notifier.Client notifier = new Notifier.Client(binaryProtocol); //_transport.open(); try { _token = notifier.bind(_url.getAuthority()); _notifier = notifier; LOG.info(notifier + " is connectioned to token=" + _token); } catch (BindException e) { // TODO: rebind } } return _notifier; } public void execute(SipServletRequest invite) throws ScriptException, IOException { setLogContext(invite); ((RemoteApplicationManager)getManager()).execute(invite, this); } protected void _execute(SipServletRequest invite) throws TException, TropoException, SystemException, AuthenticationException { SipApplicationSession appSession = invite.getApplicationSession(); SimpleCallFactory factory = new SimpleCallFactory(this, appSession); appSession.setAttribute(CALL_FACTORY, factory); CallImpl call = new SimpleIncomingCall(factory, invite, this); LOG.info("Alerting " + this + " for incoming " + call); AlertStruct alert = new AlertStruct(call.getId(), getApplicationID(), call.getCallerId(), call.getCallerName(), call.getCalledId(), call.getCalledName()); if (LOG.isDebugEnabled()) { LOG.debug("Sending AlertStruct:" + alert.toString()); } _calls.put(call.getId(), call); getNotifier().alert(_token, alert); } public void execute(HttpServletRequest invite) throws ScriptException, IOException { setLogContext(invite); ((RemoteApplicationManager)getManager()).execute(invite, this); } protected void _execute(HttpServletRequest invite) throws TException, TropoException, SystemException, AuthenticationException { } public boolean isProxy() { return _mgr == null; } public List<SipURI> getContacts() { return _contacts; } public String getApplicationKey() { return _key; } Call getCall(String id) throws TropoException { Call call = _calls.get(id); if (call == null) { throw new TropoException("Invalid call id: " + id); } return call; } void startCallRecording(String id, String filenameOrUrl, String format, String publicKey, String publicKeyUri) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { ((IncomingCall) call).startCallRecording(filenameOrUrl, format, publicKey, publicKeyUri); } catch (ClassCastException e) { throw new TropoException(call + " can not be startCallRecording."); } catch (ErrorException e) { throw new TropoException(e.toString()); } catch (FatalException e) { throw new SystemException(e.toString()); } } void stopCallRecording(String id) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { ((IncomingCall) call).stopCallRecording(); } catch (ClassCastException e) { throw new TropoException(call + " can not be stopCallRecording."); } catch (ErrorException e) { throw new TropoException(e.toString()); } catch (FatalException e) { throw new SystemException(e.toString()); } } void answer(String id, int timeout) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { ((IncomingCall)call).answer(timeout); } catch(ClassCastException e) { throw new TropoException(call + " can not be answered."); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } void reject(String id) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { ((IncomingCall)call).reject(); } catch(ClassCastException e) { throw new TropoException(call + " can not be rejected."); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } HangupStruct hangup(String id) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { call.hangup(); return new HangupStruct(); //TODO: } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } void log(String id, String msg) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { call.log(msg); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } CallImpl transfer(String id, TransferStruct t) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { return (CallImpl)call.transfer(t.getTo(), t.getFrom(), t.isAnswerOnMedia(), t.getTimeout(), t.getTtsOrUrl(), t.getRepeat(), t.getGrammar()); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } Map<String, String> prompt(String id, PromptStruct prompt) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { Map<String, String> result = call.prompt(prompt.getTtsOrUrl(), prompt.isBargein(), prompt.getGrammar(), prompt.getConfidence(), prompt.getMode(), prompt.getWait()); // System.out.println(result); if (LOG.isDebugEnabled()) { LOG.debug(result.toString()); } return result; } catch(ErrorException e) { System.out.println(e.toString()); throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } void redirect(String id, String number) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { ((IncomingCall)call).redirect(number); } catch(ClassCastException e) { throw new TropoException(call + " can not be answered."); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } void block(String id, int timeout) throws TropoException, SystemException { setLogContext(null); Call call = getCall(id); try { call.block(timeout); } catch(ErrorException e) { throw new TropoException(e.toString()); } catch(FatalException e) { throw new SystemException(e.toString()); } } CallImpl call(String from, String to, boolean answerOnMedia, int timeout, String recordUri, String recordFormat) throws TropoException, SystemException { setLogContext(null); SipApplicationSession appSession = getSipFactory().createApplicationSession(); SimpleCallFactory factory = new SimpleCallFactory(this, appSession); appSession.setAttribute(CALL_FACTORY, factory); CallImpl call = factory.call(from, to, answerOnMedia, timeout, recordUri, recordFormat); _calls.put(call.getId(), call); return call; } public boolean isActive() { try { getNotifier().heartbeat(_token); _beats = 0; return true; } catch (Throwable e) { _beats++; LOG.error(e); return _beats > 2 ? false : true; } } }