package com.voxeo.tropo.remote; import java.net.InetAddress; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import org.apache.thrift.TException; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.transport.TServerSocket; import org.apache.thrift.transport.TServerTransport; import com.voxeo.tropo.Configuration; import com.voxeo.tropo.remote.impl.TropoCloud; import com.voxeo.tropo.remote.impl.TropoIncomingCall; import com.voxeo.tropo.thrift.AlertStruct; import com.voxeo.tropo.thrift.AuthenticationException; import com.voxeo.tropo.thrift.BindException; import com.voxeo.tropo.thrift.BindStruct; import com.voxeo.tropo.thrift.HangupStruct; import com.voxeo.tropo.thrift.Notifier; import com.voxeo.tropo.thrift.SystemException; import com.voxeo.tropo.thrift.Notifier.Processor; import com.voxeo.tropo.util.ScriptThreadPoolExecutor; import com.voxeo.tropo.util.Utils; public class Tropo implements Notifier.Iface, Runnable { public static final String ACCOUNT_ID = "accountID"; public static final String APPLICATION_ID = "applicationID"; public static final String REMOTE_HOST = "remoteHost"; public static final String REMOTE_PORT = "remotePort"; public static final String LOCAL_HOST = "localHost"; public static final String LOCAL_PORT = "localPort"; public static final String BLOCKING = "blocking"; public static final String AUTO_RESTART = "auto_restart"; public static final String USER = "username"; public static final String PASSWORD = "password"; protected BindStruct _bind; protected TServerTransport _transport; protected TropoListener _listener; protected TServer _server; protected TropoCloud _tropo; protected String _token; protected TropoProperties _properties; protected boolean _shutdown = false; protected Processor _notifier; protected int _heartbeats = 0; protected Object _systemWaiter; protected Object _monitorWaiter; protected ExecutorService _pool = new ScriptThreadPoolExecutor(Configuration.get().getThreadSize() / 4, Configuration .get().getThreadSize(), new ThreadFactory() { public Thread newThread(final Runnable r) { final Thread t = new Thread(new ThreadGroup("Scripts"), r, "Script", 0); if (t.isDaemon()) { t.setDaemon(true); } if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } }); public TropoListener getListener() { return _listener; } public void setListener(TropoListener listener) { _listener = listener; } public synchronized void startup(Properties props) { _properties = new TropoProperties(props); _notifier = new Notifier.Processor(this); restart(); System.out.println("Successfully connected to Tropo clouds."); new Thread(new Monitor(), "Tropo Monitor").start(); if (_properties.getPropertyAsBoolean(BLOCKING)) { _systemWaiter = new Object(); while(!_shutdown) { synchronized(_systemWaiter) { try { _systemWaiter.wait(); } catch(InterruptedException e) { // ignore } } } } } public synchronized void shutdown() { _shutdown = true; _pool.shutdown(); try { _tropo.unbind(); _tropo.disconnect(); _server.stop(); _transport.close(); } catch(Exception e) { //ignore } if (_systemWaiter != null) { _systemWaiter.notifyAll(); } if (_monitorWaiter != null) { _monitorWaiter.notifyAll(); } } synchronized void restart() { _bind = new BindStruct(); _bind.setAccountId(_properties.getPropertyAsInt(ACCOUNT_ID)); _bind.setApplicationId(_properties.getProperty(APPLICATION_ID)); _bind.setUser(_properties.getProperty(USER)); _bind.setPassword(_properties.getProperty(PASSWORD)); _bind.setHost(_properties.getProperty(LOCAL_HOST)); _bind.setPort(_properties.getPropertyAsInt(LOCAL_PORT)); _bind.setSecret(Utils.getGUID()); _token = Utils.getGUID(); String host = _properties.getProperty(REMOTE_HOST); int port = _properties.getPropertyAsInt(REMOTE_PORT); try { _tropo = new TropoCloud(host, port, _bind); _transport = new TServerSocket(_bind.getPort()); _server = new TThreadPoolServer(_notifier, _transport); new Thread(this, "Thrift").start(); } catch(Exception e) { throw new RuntimeException(e); } } @SuppressWarnings("serial") static class TropoProperties extends Properties { static final Properties DEFAULTS = new Properties(); static { DEFAULTS.put(REMOTE_HOST, "127.0.0.1"); DEFAULTS.put(REMOTE_PORT, "9090"); try { DEFAULTS.put(LOCAL_HOST, InetAddress.getLocalHost().toString()); } catch(Exception e) { DEFAULTS.put(LOCAL_HOST, "127.0.0.1"); } DEFAULTS.put(LOCAL_PORT, "9091"); DEFAULTS.put(BLOCKING, "true"); DEFAULTS.put(AUTO_RESTART, "true"); } public TropoProperties(Properties props) { super(DEFAULTS); putAll(props); } public String getProperty(String key) { String value = super.getProperty(key); if (value == null) { throw new IllegalArgumentException("Invalid " + key + " value: " + value); } return value; } public int getPropertyAsInt(String key) { String value = getProperty(key); try { return Integer.parseInt(value); } catch(Exception e) { throw new IllegalArgumentException("Invalid " + key + " value: " + value); } } public boolean getPropertyAsBoolean(String key) { String value = getProperty(key); try { return Boolean.parseBoolean(value); } catch(Exception e) { throw new IllegalArgumentException("Invalid " + key + " value: " + value); } } } class Monitor implements Runnable { public void run() { _monitorWaiter = new Object(); while(!_shutdown) { try { _tropo.heartbeat(); _heartbeats = 0; } catch (Throwable t) { _heartbeats++; if (_heartbeats > 2) { if (_properties.getPropertyAsBoolean(AUTO_RESTART)) { System.out.println("Disconnected from Tropo clouds. Reconnecting..."); try { restart(); System.out.println("Successfully reconnected to Tropo clouds."); } catch(Throwable x) { System.out.println("Unable to reconnect to Tropo clouds. Retry in 5 seconds."); } } else { System.out.println("Disconnected from Tropo clouds. Please restart the system."); } } } synchronized(_monitorWaiter) { try { _monitorWaiter.wait(5000); } catch(InterruptedException e) { // ignore } } } } } public void alert(String token, AlertStruct alert) throws TException { if (_token.equals(token) && _listener != null) { final Call call = new TropoIncomingCall((TropoCloud)_tropo.clone(), alert); _pool.execute(new Runnable() { public void run() { _listener.onCall(call); } }); } } public String bind(String secrete) throws AuthenticationException, BindException, SystemException, TException { if (_bind.getSecret().equals(secrete)) { return _token; } throw new AuthenticationException(); } public void heartbeat(String token) throws AuthenticationException, SystemException, TException { if (_token.equals(token)) { return; } throw new AuthenticationException(); } public void unbind(String token) throws TException { if (_token.equals(token)) { System.out.println("Disconnected by Tropo clouds."); _tropo.unbind(); return; } } public void run() { try { _server.serve(); } catch(Throwable t) { t.printStackTrace(); } } public void hangup(String token, String id, HangupStruct hangup) throws TException { } }