Imported from dev1.link2tek.net CommEngine.git
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

790 строки
24 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.Enumeration;
  8. import java.util.HashMap;
  9. import java.util.Iterator;
  10. import java.util.Map;
  11. import java.util.Properties;
  12. import java.util.Vector;
  13. import java.util.concurrent.ScheduledExecutorService;
  14. import java.util.regex.Matcher;
  15. import java.util.regex.Pattern;
  16. import org.apache.log4j.Logger;
  17. import org.apache.log4j.PropertyConfigurator;
  18. import org.json.simple.JSONObject;
  19. import org.json.simple.parser.JSONParser;
  20. import org.json.simple.parser.ParseException;
  21. import altk.comm.engine.Broadcast.BroadcastState;
  22. import jakarta.servlet.ServletContext;
  23. import jakarta.servlet.ServletException;
  24. import jakarta.servlet.http.HttpServlet;
  25. import jakarta.servlet.http.HttpServletRequest;
  26. import jakarta.servlet.http.HttpServletResponse;
  27. @SuppressWarnings("serial")
  28. public abstract class CommEngine extends HttpServlet
  29. {
  30. static final String REQUEST_TOP_ELEMENT_NAME_DEFAULT = "Request";
  31. private static final long DEAD_BROADCAST_VIEWING_PERIOD_DEFAULT = 60;
  32. public static final String SERVICE_THREADPOOL_SIZE_KEY = "service_threadpool_size";
  33. private static final int SERVICE_THREADPOOL_SIZE_DEFAULT = 1;
  34. private static final String POSTBACK_THREADPOOL_SIZE_KEY = "postback_threadpool_size";
  35. private static final int POSTBACK_THREADPOOL_SIZE_DEFAULT = 20;
  36. private static final String POSTBACK_MAX_QUEUE_SIZE_KEY = "postback_max_queue_size";
  37. private static final int POSTBACK_MAX_QUEUE_SIZE_DEFAULT = 10000;
  38. private static final String POSTBACK_MAX_BATCH_SIZE_KEY = "postback_max_batch_size";
  39. private static final int POSTBACK_MAX_BATCH_SIZE_DEFAULT = 100;
  40. private static final String PAUSE_THRESHOLD_KEY = "pause_threshold";
  41. private static final int PAUSE_THRESHOLD_DEFAULT = 0;
  42. /**
  43. * Maps a broadcastId to a broadcast.
  44. */
  45. private Map<String, Broadcast> broadcasts;
  46. protected boolean notInService;
  47. protected Properties config;
  48. protected final String engineName; // e.g. "broadcast_sms", "broadcast_voice"
  49. private long startupTimestamp;
  50. // Sequencing naming of broadcast that fails to yield its broadcastId
  51. private int unknownBroadcastIdNdx = 1;
  52. /**
  53. * Used to communicate media-specific platform resources to broadcasts
  54. */
  55. protected EngineResources resources;
  56. protected static Logger myLogger;
  57. private ScheduledExecutorService scheduler;
  58. private long deadBroadcastViewingMinutes;
  59. private int completedJobCount = 0;
  60. protected String runtimeDirPath;
  61. protected String confDirPath;
  62. private DailyClock dailyClock;
  63. protected class DailyClock extends Thread
  64. {
  65. private static final String DAILY_CLOCK_THREAD_NAME = "DailyClock";
  66. private boolean threadShouldStop = false;
  67. DailyClock()
  68. {
  69. setName(DAILY_CLOCK_THREAD_NAME);
  70. }
  71. public void run()
  72. {
  73. try
  74. {
  75. while (!threadShouldStop)
  76. {
  77. // Check for pause
  78. for (Broadcast broadcast : broadcasts.values())
  79. {
  80. broadcast.enforceOperationHours();
  81. }
  82. // sleep till next wall clock minute
  83. long currentTime = System.currentTimeMillis();
  84. long sleepTime = 60000 - currentTime % 60000;
  85. if (sleepTime > 0)
  86. {
  87. try
  88. {
  89. Thread.sleep(sleepTime);
  90. }
  91. catch (Exception e)
  92. {
  93. myLogger.error("DailyClock thread caught: " + e.getMessage(), e);
  94. return;
  95. }
  96. }
  97. }
  98. }
  99. catch (Throwable t)
  100. {
  101. myLogger.error(DAILY_CLOCK_THREAD_NAME + " thread caught: " + t.getMessage(), t);
  102. }
  103. }
  104. public void terminate()
  105. {
  106. threadShouldStop = true;
  107. }
  108. }
  109. abstract protected Broadcast mkBroadcast();
  110. public CommEngine(String engineName)
  111. {
  112. this.engineName = engineName;
  113. broadcasts = new HashMap<String, Broadcast>();
  114. startupTimestamp = System.currentTimeMillis();
  115. }
  116. /**
  117. * Relocates a filepath relative to runtime directory if filepath is not absolute.
  118. * @param filepath
  119. * @return
  120. */
  121. public String relocateToRuntimeDir(String filepath)
  122. {
  123. if (filepath.startsWith("/")) return filepath; // no change to absolute path
  124. String relocated = filepath;
  125. // The next 2 lines take care of pre-git era meaning convention of filepath in properties.
  126. // Then, the runtime is relative to the current working directory of tomcat.
  127. // Now they are relative to the runtimDirPath obtained from the tomcat tomcat context.
  128. // These 2 lines an be deleted when all CommEngines in production are in the git era.
  129. String unwanted_prefix = engineName + "/";
  130. if (filepath.startsWith(unwanted_prefix)) relocated = filepath.substring(unwanted_prefix.length());
  131. relocated = runtimeDirPath + "/" + relocated;
  132. return relocated;
  133. }
  134. /**
  135. * Invoked by servlet container during initialization of servlet.
  136. * @throws ServletException
  137. */
  138. public final void init() throws ServletException
  139. {
  140. // check init parameters
  141. ServletContext servletContext = getServletContext();
  142. confDirPath = servletContext.getInitParameter(getConfDirContextName());
  143. System.out.println("Config directory is configured to be '" + confDirPath + "'. Make sure it and its content are readable by user 'tomcat'");
  144. runtimeDirPath = servletContext.getInitParameter(getRunTimeDirContextName());
  145. System.out.println("Runtime directory is configured to be '" + runtimeDirPath + "'. Make sure it and its content are readable by user 'tomcat'");
  146. File propertiesFile = new File(confDirPath + "/properties");
  147. // Configure log4j using log4j.properties file, \
  148. // sibling to the engine properties file.
  149. // This change is backward compatible with placing the log4j.properties file in
  150. // the class path.
  151. String log4j_properties = confDirPath + "/log4j.properties";
  152. // Relocate file property offsetting it by the runtimeDirPath
  153. try
  154. {
  155. Properties prop = new Properties();
  156. prop.load(new FileInputStream(log4j_properties));
  157. Enumeration<Object> e = prop.keys();
  158. while (e.hasMoreElements())
  159. {
  160. String key = (String)e.nextElement();
  161. if (key.toLowerCase().endsWith(".file"))
  162. {
  163. String filepath = prop.getProperty(key);
  164. String relocate = relocateToRuntimeDir(filepath);
  165. prop.setProperty(key, relocate);
  166. System.out.println(key + "=" + relocate);
  167. }
  168. }
  169. PropertyConfigurator.configure(prop);
  170. }
  171. catch (Exception e)
  172. {
  173. System.out.println("Failed to configure log4: " + e);
  174. // Do nothing, assuming the exception is FileNotFoundException.
  175. // Remaining log4j initialization will look for log4j.properties
  176. // file in the class path.
  177. }
  178. // This activates Logger class instantiation. At this point, if lo4j
  179. // is not yet configured,
  180. // it will look for the log4j.properties file in the class path.
  181. myLogger = Logger.getLogger(CommEngine.class);
  182. myLogger.info("init() invoked");
  183. CommonLogger.startup.info("Using lo4j properites file " + log4j_properties);
  184. CommonLogger.startup.info("Using configuration file " + propertiesFile.getAbsolutePath());
  185. config = new Properties();
  186. try
  187. {
  188. config.load(new FileInputStream(propertiesFile));
  189. }
  190. catch (FileNotFoundException e)
  191. {
  192. CommonLogger.alarm.fatal("Properties file " + propertiesFile.getAbsolutePath() + " not found -- abort");
  193. notInService = true;
  194. return;
  195. }
  196. catch (IOException e)
  197. {
  198. CommonLogger.alarm.fatal("Problem in reading properties file " + propertiesFile.getAbsolutePath() + ": " + e.getMessage());
  199. notInService = true;
  200. return;
  201. }
  202. CommonLogger.startup.info(String.format("Dead broadcast viewing period: %d minutes", deadBroadcastViewingMinutes));
  203. CommonLogger.startup.info(String.format("service thread pool size: %d", getServiceThreadPoolSize()));
  204. CommonLogger.activity.info("Postback max queue size = " + getPostbackMaxQueueSize());
  205. CommonLogger.activity.info("Postback threadpool size = " + getPostbackSenderPoolSize());
  206. CommonLogger.activity.info("Postback max batch size = " + getPostbackMaxBatchSize());
  207. dailyClock = new DailyClock();
  208. dailyClock.start();
  209. try
  210. {
  211. // Set up periodic purge of stale broadcasts, based on deadBroadcastViewingMinutes
  212. String periodStr = config.getProperty("dead_broadcast_viewing_period", Long.toString(DEAD_BROADCAST_VIEWING_PERIOD_DEFAULT));
  213. deadBroadcastViewingMinutes = Long.parseLong(periodStr);
  214. initChild();
  215. }
  216. catch (Exception e)
  217. {
  218. throw new ServletException(e.getMessage(), e);
  219. }
  220. }
  221. public int getPauseThreshold()
  222. {
  223. return getPauseThreshold(config);
  224. }
  225. public int getPauseThreshold(Properties properties)
  226. {
  227. String str = properties.
  228. getProperty(PAUSE_THRESHOLD_KEY, String.valueOf(PAUSE_THRESHOLD_DEFAULT));
  229. int pauseThreshold = Integer.valueOf(str);
  230. return pauseThreshold;
  231. }
  232. public int getServiceThreadPoolSize()
  233. {
  234. return getServiceThreadPoolSize(config);
  235. }
  236. public int getServiceThreadPoolSize(Properties properties)
  237. {
  238. String str = properties.
  239. getProperty(SERVICE_THREADPOOL_SIZE_KEY, String.valueOf(SERVICE_THREADPOOL_SIZE_DEFAULT));
  240. int size = Integer.valueOf(str);
  241. return size;
  242. }
  243. public int getPostbackSenderPoolSize()
  244. {
  245. return getPostbackSenderPoolSize(config);
  246. }
  247. public int getPostbackSenderPoolSize(Properties properties)
  248. {
  249. String str = properties.
  250. getProperty(POSTBACK_THREADPOOL_SIZE_KEY, String.valueOf(POSTBACK_THREADPOOL_SIZE_DEFAULT));
  251. int size = Integer.valueOf(str);
  252. return size;
  253. }
  254. public int getPostbackMaxQueueSize()
  255. {
  256. return getPostbackMaxQueueSize(config);
  257. }
  258. public int getPostbackMaxQueueSize(Properties properties)
  259. {
  260. String str = properties.
  261. getProperty(POSTBACK_MAX_QUEUE_SIZE_KEY, String.valueOf(POSTBACK_MAX_QUEUE_SIZE_DEFAULT));
  262. int size = Integer.valueOf(str);
  263. return size;
  264. }
  265. public int getPostbackMaxBatchSize()
  266. {
  267. return getPostbackMaxBatchSize(config);
  268. }
  269. public int getPostbackMaxBatchSize(Properties properties)
  270. {
  271. String str = properties.
  272. getProperty(POSTBACK_MAX_BATCH_SIZE_KEY, String.valueOf(POSTBACK_MAX_BATCH_SIZE_DEFAULT));
  273. int size = Integer.valueOf(str);
  274. return size;
  275. }
  276. protected void purgeStaleBroadcasts()
  277. {
  278. long now = System.currentTimeMillis();
  279. synchronized (broadcasts)
  280. {
  281. Iterator<String> iter = broadcasts.keySet().iterator();
  282. while (iter.hasNext())
  283. {
  284. Broadcast broadcast = broadcasts.get(iter.next());
  285. if (broadcast.getState().isFinal &&
  286. now - broadcast.changeStateTime > deadBroadcastViewingMinutes * 60 * 1000)
  287. {
  288. completedJobCount += broadcast.getCompletedJobCount();
  289. iter.remove();
  290. }
  291. }
  292. }
  293. }
  294. /**
  295. *
  296. * @return name of parameter in Tomcat context file, specifying properties file
  297. * for this SMSEngine.
  298. */
  299. protected abstract String getConfDirContextName();
  300. protected abstract String getRunTimeDirContextName();
  301. @Override
  302. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  303. {
  304. Broadcast broadcast = mkBroadcast();
  305. broadcast.doPost(request, response, this);
  306. }
  307. /**
  308. * Functions covered are
  309. * get=status
  310. * get=cancel_broadcast&broadcast_id=XXX
  311. */
  312. @Override
  313. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  314. {
  315. PrintWriter out;
  316. try
  317. {
  318. out = response.getWriter();
  319. }
  320. catch (IOException e)
  321. {
  322. CommonLogger.alarm.error("Cannot write a reply: " + e);
  323. return;
  324. }
  325. try
  326. {
  327. String get = (String)request.getParameter("get");
  328. if (get == null)
  329. {
  330. throw new Exception("No 'get' parameter in HTTP GET quierysring");
  331. }
  332. if (get.equalsIgnoreCase("status"))
  333. {
  334. response.setContentType("application/xml");
  335. getStatus(request, out);
  336. }
  337. else if (get.equalsIgnoreCase("cancel_broadcast"))
  338. {
  339. cancelBroadcast(request, out);
  340. }
  341. else if (get.equalsIgnoreCase("pause_broadcast"))
  342. {
  343. pauseBroadcast(request, out);
  344. }
  345. else if (get.equalsIgnoreCase("resume_broadcast"))
  346. {
  347. resumeBroadcast(request, out);
  348. }
  349. else if (get.equalsIgnoreCase("configuration"))
  350. {
  351. getConfiguration(request, out);
  352. }
  353. else if (get.equalsIgnoreCase("configure"))
  354. {
  355. configure(request, out);
  356. }
  357. else
  358. {
  359. out.write(get + " not supported");
  360. }
  361. out.close();
  362. }
  363. catch (Exception e)
  364. {
  365. myLogger.warn("While handling HTTP GETE: " + e.getMessage(), e);
  366. // Return http status BAD REQUEST
  367. int httpStatus = HttpServletResponse.SC_BAD_REQUEST;
  368. try
  369. {
  370. response.sendError(httpStatus);
  371. }
  372. catch (IOException e1)
  373. {
  374. myLogger.warn("Unnable to return HTTP error code " + httpStatus, e1);
  375. }
  376. }
  377. }
  378. /**
  379. *
  380. *
  381. * Writes configuration in JSON string to out
  382. * @param request
  383. * @param out
  384. */
  385. protected void getConfiguration(HttpServletRequest request, PrintWriter out)
  386. {
  387. JSONObject configuration = getConfigJSON();
  388. out.print(configuration);
  389. }
  390. @SuppressWarnings("unchecked")
  391. private JSONObject getConfigJSON()
  392. {
  393. JSONObject config = new JSONObject();
  394. // broadcast configuration
  395. JSONObject broadcastsConfig = new JSONObject();
  396. synchronized (broadcasts) {
  397. for (String broadcastId : broadcasts.keySet())
  398. {
  399. Broadcast broadcast = broadcasts.get(broadcastId);
  400. if (broadcast.getState().isFinal) continue;
  401. broadcastsConfig.put(broadcastId, broadcast.getConfigJSON());
  402. }
  403. }
  404. if (broadcastsConfig.size() > 0) config.put("broadcasts", broadcastsConfig);
  405. childAddConfig(config);
  406. return config;
  407. }
  408. /**
  409. * Derived class may add to configMap
  410. * @param configMap
  411. */
  412. protected void childAddConfig(JSONObject config)
  413. {
  414. }
  415. private void cancelBroadcast(HttpServletRequest request, PrintWriter out)
  416. {
  417. // Get broadcastId from request
  418. String broadcastId = getBroadcastId(request);
  419. Broadcast broadcast = broadcasts.get(broadcastId);
  420. String reason = request.getParameter("reason");
  421. if (broadcast == null)
  422. {
  423. out.format("Broadcast %s does not exist", broadcastId);
  424. return;
  425. }
  426. broadcast.cancel(reason, out);
  427. }
  428. protected void pauseBroadcast(HttpServletRequest request, PrintWriter out)
  429. {
  430. // Get broadcastId from request
  431. String broadcastId = getBroadcastId(request);
  432. Broadcast broadcast = broadcasts.get(broadcastId);
  433. String reason = request.getParameter("reason");
  434. if (broadcast == null)
  435. {
  436. out.format("Broadcast %s does not exist", broadcastId);
  437. return;
  438. }
  439. broadcast.pause(reason, out);
  440. }
  441. protected void resumeBroadcast(HttpServletRequest request, PrintWriter out)
  442. {
  443. // Get broadcastId from request
  444. String broadcastId = getBroadcastId(request);
  445. Broadcast broadcast = broadcasts.get(broadcastId);
  446. String reason = request.getParameter("reason");
  447. if (broadcast == null)
  448. {
  449. out.format("Broadcast %s does not exist", broadcastId);
  450. return;
  451. }
  452. broadcast.resume(reason, out);
  453. }
  454. /**
  455. * Check if timeOfDay is of the form "HH::mm"
  456. * @param timeOfDay
  457. * @return timeOfDay if valid, otherwise null
  458. */
  459. protected static String checkTimeOfDay(String timeOfDay) {
  460. timeOfDay = timeOfDay.trim();
  461. // pattern hh:mm
  462. Pattern pattern = Pattern.compile("^(\\d+):([0-5]\\d)$");
  463. Matcher matcher = pattern.matcher(timeOfDay);
  464. if (!matcher.find()) return null;
  465. // Check hour in range
  466. String hh = matcher.group(1).trim();
  467. if (Integer.parseInt(hh) > 23) return null;
  468. return (hh.length()==1? "0" + hh : hh) + ":" + matcher.group(2);
  469. }
  470. /**
  471. * Writes error message to out. Otherwise writes nothing to out.
  472. * @param request
  473. * @param out
  474. */
  475. protected void configure(HttpServletRequest request, PrintWriter out) {
  476. // save original configuration for roll back in case of error
  477. JSONObject origConfigJSON = getConfigJSON();
  478. String jsonString = request.getParameter("data");
  479. try {
  480. JSONParser parser = new JSONParser();
  481. JSONObject configuration = (JSONObject) parser.parse(jsonString);
  482. configure(configuration);
  483. } catch (Exception e) {
  484. String errMsg =
  485. (e instanceof ParseException)? ("JSON error: " + e) : e.getMessage();
  486. myLogger.error(errMsg, e);
  487. out.write("Error - " + errMsg);
  488. // restore current confiuration
  489. try {
  490. configure(origConfigJSON);
  491. } catch (Exception e1) {
  492. myLogger.error("Internal error in restoring original configuration: " + e1.getMessage(), e1);
  493. out.write("\nInternal error in restoring original configuration: " + e1.getMessage());
  494. }
  495. }
  496. }
  497. private void configure(JSONObject configuration) throws Exception {
  498. // broadcasts
  499. JSONObject broadcastsConfig = (JSONObject)configuration.get("broadcasts");
  500. if (broadcastsConfig != null) {
  501. for (Object broadcastId : broadcastsConfig.keySet())
  502. {
  503. JSONObject broadcastConfig = (JSONObject)broadcastsConfig.get(broadcastId);
  504. Broadcast broadcast = broadcasts.get(broadcastId);
  505. if (broadcast == null) continue;
  506. broadcast.configure(broadcastConfig);
  507. }
  508. }
  509. // derived class
  510. configureChild(configuration);
  511. }
  512. /**
  513. * Derived class updates itself from given configuration.
  514. * @param configuration
  515. */
  516. protected void configureChild(JSONObject configuration)
  517. {
  518. }
  519. /**
  520. * <CallEngine_status>
  521. * status of each broadcast
  522. * <calls><total>ttt</total><connected>nnn</connected>
  523. * </CallEngine_status>
  524. */
  525. private void getStatus(HttpServletRequest request, PrintWriter out)
  526. {
  527. purgeStaleBroadcasts();
  528. String broadcastId = request.getParameter("broadcast_id");
  529. if (broadcastId != null)
  530. {
  531. broadcastId = broadcastId.trim();
  532. if (broadcastId.length() == 0)
  533. {
  534. out.write("broadcast_id request parameter cannot be empty");
  535. return;
  536. }
  537. Broadcast broadcast = broadcasts.get(broadcastId);
  538. if (broadcast == null)
  539. {
  540. out.write("<error>No such broadcast</error>");
  541. }
  542. else
  543. {
  544. out.write(broadcast.mkStatusReport());
  545. }
  546. return;
  547. }
  548. else
  549. {
  550. String tag = engineName + "_status";
  551. out.write("<" + tag + ">\r\n");
  552. out.write("<startup_time>" + startupTimestamp
  553. + "</startup_time>\r\n");
  554. // First get a copy of broadcasts, to avoid mutex deadlock.
  555. Vector<Broadcast> broadcastList = new Vector<Broadcast>();
  556. synchronized(broadcasts)
  557. {
  558. for (String key : broadcasts.keySet())
  559. {
  560. broadcastList.add(broadcasts.get(key));
  561. }
  562. }
  563. // We have released the lock.
  564. // Then append status of each broadcast to outBuf.
  565. for (Broadcast broadcast : broadcastList)
  566. {
  567. out.write(broadcast.mkStatusReport() + "\n");
  568. }
  569. out.write("<job_summary completed='" + getCompletedJobCount() + "' pending='" + getPendingJobCount() + "' active='" + getActiveJobCount() + "'/>\n");
  570. out.write("</" + tag + ">");
  571. }
  572. }
  573. public int getPendingJobCount()
  574. {
  575. int readyCount = 0;
  576. synchronized(broadcasts)
  577. {
  578. for (Broadcast broadcast : broadcasts.values())
  579. {
  580. readyCount += broadcast.getPendingJobCount();
  581. }
  582. }
  583. return readyCount;
  584. }
  585. public int getActiveJobCount()
  586. {
  587. int activeCount = 0;
  588. synchronized(broadcasts)
  589. {
  590. for (Broadcast broadcast : broadcasts.values())
  591. {
  592. activeCount += broadcast.getActiveJobCount();
  593. }
  594. }
  595. return activeCount;
  596. }
  597. public int getCompletedJobCount()
  598. {
  599. int additionalCompletedJobCount = 0;
  600. synchronized(broadcasts)
  601. {
  602. for (Broadcast broadcast : broadcasts.values())
  603. {
  604. additionalCompletedJobCount += broadcast.getCompletedJobCount();
  605. }
  606. }
  607. return completedJobCount + additionalCompletedJobCount;
  608. }
  609. public void removeBroadcast(String broadcastId)
  610. {
  611. CommonLogger.activity.info("Removing broadcast " + broadcastId);
  612. synchronized(broadcasts)
  613. {
  614. broadcasts.remove(broadcastId);
  615. }
  616. }
  617. public boolean notInService()
  618. {
  619. return notInService;
  620. }
  621. /**
  622. * Decode http GET request for broadcast_id value
  623. * @param request
  624. * @return broadcast_id
  625. */
  626. private String getBroadcastId(HttpServletRequest request)
  627. {
  628. return request.getParameter("broadcast_id");
  629. }
  630. /**
  631. * Invoked by servlet container when servlet is destroyed.
  632. */
  633. public final void destroy()
  634. {
  635. System.out.println("Destroying " + engineName);
  636. // Shutdown threads that periodically purge stale broadcasts.
  637. scheduler.shutdownNow();
  638. synchronized(broadcasts)
  639. {
  640. for (Broadcast broadcast : broadcasts.values())
  641. {
  642. broadcast.terminate(BroadcastState.ABORTED, "Platform termination");
  643. }
  644. }
  645. // Destroy dailyClock thread
  646. try
  647. {
  648. dailyClock.terminate();
  649. dailyClock.join();
  650. }
  651. catch (InterruptedException e)
  652. {
  653. // TODO nothing
  654. }
  655. destroyChild();
  656. super.destroy();
  657. }
  658. /**
  659. * Indirectly invoked by servlet container during servlet initialization.
  660. */
  661. abstract protected void initChild();
  662. /**
  663. * Indirectly invoked by servlet container during destruction of servlet.
  664. */
  665. abstract protected void destroyChild();
  666. public EngineResources getResources()
  667. {
  668. return resources;
  669. }
  670. public void addBroadcast(String broadcastId, Broadcast broadcast)
  671. {
  672. if (broadcastId == null) broadcastId = "Unknown" + unknownBroadcastIdNdx++;
  673. synchronized (broadcasts)
  674. {
  675. broadcasts.put(broadcastId, broadcast);
  676. }
  677. }
  678. /**
  679. * If broadcast has no id, one will be created for it.
  680. * @param broadcast
  681. */
  682. public void installBroadcast(Broadcast broadcast)
  683. {
  684. String broadcastId = broadcast.getBroadcastId();
  685. if (broadcastId == null) broadcastId = "Unknown" + unknownBroadcastIdNdx++;
  686. synchronized (broadcasts)
  687. {
  688. broadcasts.put(broadcastId, broadcast);
  689. }
  690. }
  691. }