Imported from dev1.link2tek.net CommEngine.git
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

532 lines
18 KiB

  1. package altk.comm.engine;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.IOException;
  6. import java.io.PrintWriter;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. import java.util.Properties;
  10. import java.util.Vector;
  11. import java.util.concurrent.Executors;
  12. import java.util.concurrent.ScheduledExecutorService;
  13. import java.util.concurrent.TimeUnit;
  14. import javax.servlet.ServletContext;
  15. import javax.servlet.http.HttpServlet;
  16. import javax.servlet.http.HttpServletRequest;
  17. import javax.servlet.http.HttpServletResponse;
  18. import org.apache.log4j.Logger;
  19. import altk.comm.engine.Broadcast.BroadcastState;
  20. import altk.comm.engine.exception.BroadcastError;
  21. import altk.comm.engine.exception.BroadcastException;
  22. import altk.comm.engine.exception.PlatformError;
  23. import altk.comm.engine.exception.PlatformException;
  24. import altk.comm.engine.postback.PostBack;
  25. @SuppressWarnings("serial")
  26. public abstract class CommEngine extends HttpServlet
  27. {
  28. static final String REQUEST_TOP_ELEMENT_NAME_DEFAULT = "Request";
  29. private static final int SCHEDULER_THREAD_POOL_SIZE = 1;
  30. private static final long DEAD_BROADCAST_VIEWING_PERIOD_DEFAULT = 60;
  31. private static final int SERVICE_THREADPOOL_SIZE_DEFAULT = 1;
  32. private static final int POSTBACK_THREADPOOL_SIZE_DEFAULT = 2;
  33. private static final int POSTBACK_MAX_QUEUE_SIZE_DEFAULT = 10000;
  34. private static final int POSTBACK_MAX_BATCH_SIZE_DEFAULT = 100;
  35. /**
  36. * Maps a broadcastId to a broadcast.
  37. */
  38. private Map<String, Broadcast> broadcasts;
  39. protected boolean notInService;
  40. protected Properties config;
  41. protected Map<String, PostBack> postBackMap;
  42. protected final String engineName; // e.g. "broadcast_sms", "broadcast_voice"
  43. private long startupTimestamp;
  44. // Sequencing naming of broadcast that fails to yield its broadcastId
  45. private int unknownBroadcastIdNdx = 1;
  46. private BroadcastException myException;
  47. /**
  48. * Used to communicate media-specific platform resources to broadcasts
  49. */
  50. protected EngineResources resources;
  51. private static Logger myLogger = Logger.getLogger(CommEngine.class);
  52. private ScheduledExecutorService scheduler;
  53. private long deadBroadcastViewingMinutes;
  54. private int completedJobCount = 0;
  55. private int serviceThreadPoolSize;
  56. private int postbackMaxQueueSize;
  57. private int postbackSenderPoolSize;
  58. private int postbackMaxBatchSize;
  59. abstract protected Broadcast mkBroadcast();
  60. public CommEngine(String engineName)
  61. {
  62. this.engineName = engineName;
  63. broadcasts = new HashMap<String, Broadcast>();
  64. startupTimestamp = System.currentTimeMillis();
  65. myException = null;
  66. }
  67. /**
  68. * Invoked by servlet container during initialization of servlet.
  69. */
  70. public final void init()
  71. {
  72. myLogger.info("init() invoked");
  73. // check init parameters
  74. ServletContext servletContext = getServletContext();
  75. String propertiesFilePath;
  76. propertiesFilePath = servletContext.getInitParameter(getPropertiesContextName());
  77. File propertiesFile = new File(propertiesFilePath);
  78. CommonLogger.startup.info("Using configuration file " + propertiesFile.getAbsolutePath());
  79. config = new Properties();
  80. try
  81. {
  82. config.load(new FileInputStream(propertiesFile));
  83. }
  84. catch (FileNotFoundException e)
  85. {
  86. CommonLogger.alarm.fatal("Properties file " + propertiesFile.getAbsolutePath() + " not found -- abort");
  87. notInService = true;
  88. return;
  89. }
  90. catch (IOException e)
  91. {
  92. CommonLogger.alarm.fatal("Problem in reading properties file " + propertiesFile.getAbsolutePath() + ": " + e.getMessage());
  93. notInService = true;
  94. return;
  95. }
  96. postBackMap = new HashMap<String, PostBack>();
  97. // Set up periodic purge of stale broadcasts, based on deadBroadcastViewingMinutes
  98. String periodStr = config.getProperty("dead_broadcast_viewing_period",
  99. new Long(DEAD_BROADCAST_VIEWING_PERIOD_DEFAULT).toString());
  100. deadBroadcastViewingMinutes = Long.parseLong(periodStr);
  101. CommonLogger.startup.info(String.format("Dead broadcast viewing period: %d minutes", deadBroadcastViewingMinutes));
  102. String str = config.getProperty("service_threadpool_size",
  103. new Integer(SERVICE_THREADPOOL_SIZE_DEFAULT).toString());
  104. serviceThreadPoolSize = Integer.parseInt(str);
  105. CommonLogger.startup.info(String.format("service thread pool size: %d", serviceThreadPoolSize));
  106. String string = config.getProperty("postback_max_queue_size",
  107. new Integer(POSTBACK_MAX_QUEUE_SIZE_DEFAULT).toString());
  108. postbackMaxQueueSize = Integer.parseInt(string);
  109. CommonLogger.activity.info("Postback max queue size = " + postbackMaxQueueSize);
  110. string = config.getProperty("postback_threadpool_size",
  111. new Integer(POSTBACK_THREADPOOL_SIZE_DEFAULT).toString());
  112. postbackSenderPoolSize = Integer.parseInt(string);
  113. CommonLogger.activity.info("Postback threadpool size = " + postbackSenderPoolSize);
  114. string = config.getProperty("postback_max_batch_size",
  115. new Integer(POSTBACK_MAX_BATCH_SIZE_DEFAULT).toString());
  116. postbackMaxBatchSize = Integer.parseInt(string);
  117. CommonLogger.activity.info("Postback max batch size = " + postbackMaxBatchSize);
  118. scheduler = Executors.newScheduledThreadPool(SCHEDULER_THREAD_POOL_SIZE);
  119. scheduler.scheduleAtFixedRate(new Runnable() { public void run() { purgeStaleBroadcasts();}},
  120. deadBroadcastViewingMinutes, deadBroadcastViewingMinutes, TimeUnit.MINUTES);
  121. initChild();
  122. }
  123. protected void purgeStaleBroadcasts()
  124. {
  125. long now = System.currentTimeMillis();
  126. synchronized (broadcasts)
  127. {
  128. for (String id : broadcasts.keySet())
  129. {
  130. if (broadcasts.get(id).changeStateTime - now > deadBroadcastViewingMinutes * 60 * 1000)
  131. {
  132. Broadcast broadcast = broadcasts.get(id);
  133. completedJobCount += broadcast.getCompletedJobCount();
  134. broadcasts.remove(id);
  135. }
  136. }
  137. }
  138. }
  139. /**
  140. *
  141. * @return name of parameter in Tomcat context file, specifying properties file
  142. * for this SMSEngine.
  143. */
  144. protected abstract String getPropertiesContextName();
  145. @Override
  146. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  147. {
  148. myException = null;
  149. try
  150. {
  151. Broadcast broadcast = mkBroadcast();
  152. broadcast.setServiceThreadPoolsize(serviceThreadPoolSize);
  153. try
  154. {
  155. broadcast.decode(request, notInService);
  156. if (notInService)
  157. {
  158. throw new PlatformException(PlatformError.RUNTIME_ERROR,
  159. "Not in service");
  160. }
  161. if (broadcast.recipientList.size() == 0)
  162. {
  163. CommonLogger.activity.info("Broadcast " + broadcast.getBroadcastId() + ": No recipients");
  164. broadcast.setState(BroadcastState.COMPLETED, "No recipients", null);
  165. return;
  166. }
  167. // Determine postBackUrl
  168. String postBackURL = broadcast.getPostBackURL();
  169. PostBack postBack = null;
  170. if (postBackURL != null)
  171. {
  172. postBack = postBackMap.get(postBackURL);
  173. if (postBack == null)
  174. {
  175. postBack = new PostBack(postBackURL, broadcast.broadcastType + "_status",
  176. postbackMaxQueueSize, postbackSenderPoolSize, postbackMaxBatchSize);
  177. postBackMap.put(postBackURL, postBack);
  178. }
  179. }
  180. broadcast.initSync(resources);
  181. broadcast.init(postBack);
  182. if (broadcast.getState() == BroadcastState.COMPLETED) return;
  183. }
  184. catch (BroadcastException e)
  185. {
  186. myException = e;
  187. broadcast.setState(BroadcastState.ABORTED, e.errorCodeText, e.errorText);
  188. CommonLogger.alarm.error("Broadcast aborted: " + e.getMessage());
  189. myLogger.error("Broadcast aborted", e);
  190. return;
  191. }
  192. catch (Throwable t)
  193. {
  194. // Caught stray unexpected runtime problem
  195. CommonLogger.alarm.error("Broadcast aborted: " + t);
  196. myLogger.error("Broadcast aborted", t);
  197. myException = new BroadcastException(BroadcastError.PLATFORM_ERROR, t.getMessage());
  198. broadcast.setState(BroadcastState.ABORTED, myException.errorCodeText, myException.errorText);
  199. }
  200. finally
  201. {
  202. // Put broadcast in broadcasts map.
  203. String broadcastId = broadcast.getBroadcastId();
  204. if (broadcastId != null && broadcastId.length() != 0)
  205. {
  206. broadcasts.put(broadcastId, broadcast);
  207. }
  208. else
  209. {
  210. String makeUpId = "Unknown" + unknownBroadcastIdNdx++;
  211. broadcasts.put(makeUpId, broadcast);
  212. }
  213. // Return regular or error response
  214. String responseXML = broadcast.getResponseXML(myException);
  215. PrintWriter writer = response.getWriter();
  216. writer.write(responseXML);
  217. writer.close();
  218. }
  219. try
  220. {
  221. broadcast.initAsync();
  222. broadcast.startProcessing();
  223. }
  224. catch (BroadcastException e)
  225. {
  226. broadcast.setState(BroadcastState.ABORTED, e.errorCodeText, e.errorText);
  227. CommonLogger.alarm.error("Broadcast aborted: " + e.getMessage());
  228. myLogger.error("Broadcast aborted", e);
  229. }
  230. }
  231. catch (Throwable t)
  232. {
  233. // Caught stray unexpected runtime problem
  234. CommonLogger.alarm.error("Broadcast aborted: " + t.getMessage());
  235. myLogger.error("Broadcast aborted", t);
  236. }
  237. }
  238. /**
  239. * Functions covered are
  240. * get=status
  241. * get=cancel_broadcast&broadcast_id=XXX
  242. */
  243. @Override
  244. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  245. {
  246. PrintWriter out;
  247. try
  248. {
  249. out = response.getWriter();
  250. }
  251. catch (IOException e)
  252. {
  253. CommonLogger.alarm.error("Cannot write a reply: " + e);
  254. return;
  255. }
  256. String get = (String)request.getParameter("get");
  257. if (get == null)
  258. {
  259. // Return http status BAD REQUEST
  260. int httpStatus = HttpServletResponse.SC_BAD_REQUEST;
  261. try
  262. {
  263. response.sendError(httpStatus);
  264. }
  265. catch (IOException e)
  266. {
  267. myLogger.warn("Unnable to return HTTP error code " + httpStatus);
  268. }
  269. return;
  270. }
  271. if (get.equalsIgnoreCase("status"))
  272. {
  273. getStatus(request, out);
  274. }
  275. else if (get.equalsIgnoreCase("cancel_broadcast"))
  276. {
  277. cancelBroadcast(request, out);
  278. }
  279. else if (get.equalsIgnoreCase("pause_broadcast"))
  280. {
  281. pauseBroadcast(request, out);
  282. }
  283. else if (get.equalsIgnoreCase("resume_broadcast"))
  284. {
  285. resumeBroadcast(request, out);
  286. }
  287. else
  288. {
  289. out.write(get + " not supported");
  290. }
  291. out.close();
  292. }
  293. private void cancelBroadcast(HttpServletRequest request, PrintWriter out)
  294. {
  295. // Get broadcastId from request
  296. String broadcastId = getBroadcastId(request);
  297. Broadcast broadcast = broadcasts.get(broadcastId);
  298. if (broadcast == null)
  299. {
  300. out.format("Broadcast %s does not exist", broadcastId);
  301. return;
  302. }
  303. broadcast.cancel();
  304. }
  305. protected void pauseBroadcast(HttpServletRequest request, PrintWriter out)
  306. {
  307. // Get broadcastId from request
  308. String broadcastId = getBroadcastId(request);
  309. Broadcast broadcast = broadcasts.get(broadcastId);
  310. if (broadcast == null)
  311. {
  312. out.format("Broadcast %s does not exist", broadcastId);
  313. return;
  314. }
  315. broadcast.pause();
  316. }
  317. protected void resumeBroadcast(HttpServletRequest request, PrintWriter out)
  318. {
  319. // Get broadcastId from request
  320. String broadcastId = getBroadcastId(request);
  321. Broadcast broadcast = broadcasts.get(broadcastId);
  322. if (broadcast == null)
  323. {
  324. out.format("Broadcast %s does not exist", broadcastId);
  325. return;
  326. }
  327. broadcast.resume();
  328. }
  329. /**
  330. * <CallEngine_status>
  331. * status of each broadcast
  332. * <calls><total>ttt</total><connected>nnn</connected>
  333. * </CallEngine_status>
  334. */
  335. private void getStatus(HttpServletRequest request, PrintWriter out)
  336. {
  337. String broadcastId = request.getParameter("broadcast_id");
  338. if (broadcastId != null)
  339. {
  340. broadcastId = broadcastId.trim();
  341. if (broadcastId.length() == 0)
  342. {
  343. out.write("broadcast_id request parameter cannot be empty");
  344. return;
  345. }
  346. Broadcast broadcast = broadcasts.get(broadcastId);
  347. if (broadcast == null)
  348. {
  349. out.write("<error>No such broadcast</error>");
  350. }
  351. else
  352. {
  353. out.write(broadcast.mkStatusReport());
  354. }
  355. return;
  356. }
  357. else
  358. {
  359. String tag = engineName + "_status";
  360. out.write("<" + tag + ">\r\n");
  361. out.write("<startup_time>" + startupTimestamp
  362. + "</startup_time>\r\n");
  363. // First get a copy of broadcasts, to avoid mutex deadlock.
  364. Vector<Broadcast> broadcastList = new Vector<Broadcast>();
  365. synchronized(broadcasts)
  366. {
  367. for (String key : broadcasts.keySet())
  368. {
  369. broadcastList.add(broadcasts.get(key));
  370. }
  371. }
  372. // We have released the lock.
  373. // Then append status of each broadcast to outBuf.
  374. for (Broadcast broadcast : broadcastList)
  375. {
  376. out.write(broadcast.mkStatusReport());
  377. }
  378. out.write("<job_summary completed='" + getCompletedJobCount() + "' ready='" + getReadyJobCount() + "' active='" + getActiveJobCount() + "'/>");
  379. out.write("</" + tag + ">");
  380. }
  381. }
  382. public int getReadyJobCount()
  383. {
  384. int readyCount = 0;
  385. synchronized(broadcasts)
  386. {
  387. for (Broadcast broadcast : broadcasts.values())
  388. {
  389. readyCount += broadcast.getReadyJobCount();
  390. }
  391. }
  392. return readyCount;
  393. }
  394. public int getActiveJobCount()
  395. {
  396. int activeCount = 0;
  397. synchronized(broadcasts)
  398. {
  399. for (Broadcast broadcast : broadcasts.values())
  400. {
  401. activeCount += broadcast.getActiveJobCount();
  402. }
  403. }
  404. return activeCount;
  405. }
  406. public int getCompletedJobCount()
  407. {
  408. int additionalCompletedJobCount = 0;
  409. synchronized(broadcasts)
  410. {
  411. for (Broadcast broadcast : broadcasts.values())
  412. {
  413. additionalCompletedJobCount += broadcast.getCompletedJobCount();
  414. }
  415. }
  416. return completedJobCount + additionalCompletedJobCount;
  417. }
  418. public void removeBroadcast(String broadcastId)
  419. {
  420. CommonLogger.activity.info("Removing broadcast " + broadcastId);
  421. synchronized(broadcasts)
  422. {
  423. broadcasts.remove(broadcastId);
  424. }
  425. }
  426. public boolean notInService()
  427. {
  428. return notInService;
  429. }
  430. /**
  431. * Decode http GET request for broadcast_id value
  432. * @param request
  433. * @return broadcast_id
  434. */
  435. private String getBroadcastId(HttpServletRequest request)
  436. {
  437. return request.getParameter("broadcast_id");
  438. }
  439. /**
  440. * Invoked by servlet container when servlet is destroyed.
  441. */
  442. public final void destroy()
  443. {
  444. System.out.println(engineName + " destroyed");
  445. // Shutdown threads that periodically purge stale broadcasts.
  446. scheduler.shutdownNow();
  447. // Kill threads in each PostBack, which is remembered in postBackMap.
  448. for (PostBack postback : postBackMap.values())
  449. {
  450. postback.terminate();
  451. }
  452. for (Broadcast broadcast : broadcasts.values())
  453. {
  454. broadcast.terminate(BroadcastState.ABORTED, "Platform termination");
  455. }
  456. destroyChild();
  457. super.destroy();
  458. }
  459. /**
  460. * Indirectly invoked by servlet container during servlet initialization.
  461. */
  462. abstract protected void initChild();
  463. /**
  464. * Indirectly invoked by serlet container during destruction of servlet.
  465. */
  466. abstract protected void destroyChild();
  467. }