Imported from dev1.link2tek.net CommEngine.git
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

882 行
28 KiB

  1. package altk.comm.engine;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Queue;
  8. import java.util.concurrent.Executors;
  9. import java.util.concurrent.LinkedBlockingQueue;
  10. import java.util.concurrent.ScheduledExecutorService;
  11. import java.util.concurrent.ScheduledFuture;
  12. import java.util.concurrent.TimeUnit;
  13. import javax.servlet.http.HttpServletRequest;
  14. import org.apache.log4j.Logger;
  15. import org.apache.log4j.NDC;
  16. import altk.comm.engine.exception.BroadcastException;
  17. import altk.comm.engine.exception.EngineException;
  18. import altk.comm.engine.postback.PostBack;
  19. /**
  20. * Broadcast class absorbs what was formerly known as Dispatcher class.
  21. *
  22. * @author Yuk-Ming
  23. *
  24. */
  25. public abstract class Broadcast
  26. {
  27. private static final int SCHEDULER_THREAD_POOL_SIZE = 5;
  28. private static final String ACTIVITY_RECORD_ID_PARAM_NAME_DEFAULT = "activity_record_id";
  29. public final String broadcastType;
  30. private String broadcastId;
  31. private BroadcastState state = BroadcastState.INSTALLING;
  32. String haltReason;
  33. String stateErrorText;
  34. public final long receiveTime;
  35. public long changeStateTime;
  36. private int completedJobCount = 0;
  37. protected String activityRecordIdParamName;
  38. private String jobReportRootNodeName;
  39. /**
  40. * Set when reading request XML, but never used.
  41. */
  42. private String launchRecordId;
  43. // protected XPath xpathEngine;
  44. protected String postBackURL;
  45. private PostBack postBack;
  46. public long expireTime;
  47. static Logger myLogger = Logger.getLogger(Broadcast.class);
  48. /**
  49. * This queue is designed for only one server
  50. */
  51. private Queue<Job> readyQueue;
  52. private List<Thread> serviceThreadPool;
  53. private Object resumeFlag; // Semaphore for dispatcher threads to resume.
  54. protected List<Recipient> recipientList;
  55. private int remainingJobs;
  56. private ScheduledExecutorService scheduler;
  57. private int serviceThreadPoolSize;
  58. public static enum BroadcastState
  59. {
  60. INSTALLING,
  61. RUNNING,
  62. HALTING,
  63. HALTED,
  64. CANCELING,
  65. CANCELED, // Final state
  66. PURGED, // Final state
  67. ABORTED, // final state
  68. COMPLETED // Final state
  69. }
  70. public enum StateChangeStatus
  71. {
  72. SUCCESS,
  73. NO_CHANGE,
  74. FORBIDDEN
  75. }
  76. /**
  77. * When a Broadcast is first created, its state is INSTALLING, during which
  78. * time, the broadcast request XML is digested. After this period, the
  79. * state of the broadcast enters into RUNNING, and it begins with adding
  80. * all VoiceJobs in the request to the Dispatcher's queues. During this period
  81. * calls are dispatched, setup, and terminate. When the last call is terminated,
  82. * the broadcast enters into the COMPLETED state.
  83. *
  84. * Transitions from INSTALLING to RUNNING, and from RUNNING to COMPLETED happen
  85. * automatically without any external influence.
  86. *
  87. * At any time, the broadcast may be canceled by user action, which causes
  88. * the broadcast to transition from the RUNNING state into the CANCELED state.
  89. *
  90. * Since the RUNNING state may go to the COMLETED or to the CANCELED state,
  91. * each due to a different thread, there needs to be a mutex to guarantee
  92. * state and data integrity.
  93. *
  94. * A INSTALLING or RUNNING broadcast may be paused by user action, stopping the
  95. * Dispatcher from making new calls and causing the broadcast to go to
  96. * the HALTED state. Certain error conditions result in the HALTED state.
  97. *
  98. * User may order a broadcast to be removed entirely with a purge command,
  99. * causing the broadcast to go to the PURGED state. This state is there so
  100. * objects, that has reference to a broadcast which has been purged, know
  101. * what to do in that situation.
  102. *
  103. * We need the pause operation to set a RUNNING machine to be in the PAUSING state,
  104. * allow ongoing jobs to proceed to normal termination,
  105. * whereas no new jobs are started. State goes from PAUSING to HALTED.
  106. *
  107. * Cancel-nice operation is pause, followed by the automatic transition
  108. * from HALTED to CANCELED.
  109. *
  110. * Cancel operation forces all jobs to abort, and the state transitions to CANCELED
  111. * immediately.
  112. *
  113. * Because the Dispatcher and Broadcast is one-to-one, and because they access each other's
  114. * data, Dispatcher should be combined into Broadcast.
  115. *
  116. * @author Yuk-Ming
  117. *
  118. */
  119. static Map<BroadcastState, List<BroadcastState>> toStates;
  120. static
  121. {
  122. // Initialize legal transitions of state machine.
  123. // For each state, define a list of legal states that this state can transition to
  124. toStates = new HashMap<BroadcastState, List<BroadcastState>>();
  125. // Transitions from INSTALLING
  126. toStates.put(BroadcastState.INSTALLING, Arrays.asList(
  127. BroadcastState.RUNNING, // Normal transition
  128. BroadcastState.CANCELING, // User action
  129. BroadcastState.CANCELED, // User action
  130. BroadcastState.HALTING, // User action
  131. BroadcastState.HALTED, // User action
  132. BroadcastState.PURGED, // User action
  133. BroadcastState.ABORTED, // TTS error
  134. BroadcastState.COMPLETED // When recipient list is empty
  135. ));
  136. // Transitions from RUNNING
  137. toStates.put(BroadcastState.RUNNING, Arrays.asList(
  138. BroadcastState.CANCELING, // User action
  139. BroadcastState.CANCELED, // User action
  140. BroadcastState.HALTING, // User action
  141. BroadcastState.HALTED, // User action
  142. BroadcastState.PURGED, // User action
  143. BroadcastState.ABORTED, // Service provider irrecoverable error
  144. BroadcastState.COMPLETED // Natural transition, if all ongoing calls complete and no more calls in Dispatcher queues.
  145. ));
  146. // Transitions from CANCELING
  147. toStates.put(BroadcastState.CANCELING, Arrays.asList(
  148. BroadcastState.CANCELED, // User action
  149. BroadcastState.PURGED, // User action
  150. BroadcastState.COMPLETED // Natural transition, if all ongoing calls complete and no more calls in Dispatcher queues.
  151. ));
  152. // Transitions from HALTING
  153. toStates.put(BroadcastState.HALTING, Arrays.asList(
  154. BroadcastState.RUNNING, // User action
  155. BroadcastState.CANCELED, // User action
  156. BroadcastState.HALTED,
  157. BroadcastState.PURGED, // User action
  158. BroadcastState.COMPLETED // Natural transition, if all ongoing jobs complete and no more calls in Dispatcher queues.
  159. ));
  160. // Transitions from HALTED
  161. toStates.put(BroadcastState.HALTED, Arrays.asList(
  162. BroadcastState.RUNNING, // User action
  163. BroadcastState.CANCELED, // User action
  164. BroadcastState.CANCELING, // User action
  165. BroadcastState.PURGED, // User action
  166. BroadcastState.COMPLETED // Natural transition, if all ongoing jobs complete and no more calls in Dispatcher queues.
  167. ));
  168. }
  169. public static class StateChangeResult
  170. {
  171. public StateChangeStatus stateChangeStatus;
  172. public BroadcastState currentState;
  173. public BroadcastState previousState;
  174. public StateChangeResult(StateChangeStatus stateChangeStatus,
  175. BroadcastState currentState, BroadcastState previousState)
  176. {
  177. this.stateChangeStatus = stateChangeStatus;
  178. this.currentState = currentState;
  179. this.previousState = previousState;
  180. }
  181. }
  182. protected class Service extends Thread
  183. {
  184. Object serviceProvider;
  185. protected Service(String name) throws BroadcastException
  186. {
  187. serviceProvider = getInitializedServiceProvider();
  188. setName(name);
  189. }
  190. public void run()
  191. {
  192. NDC.push(getName());
  193. for (;;)
  194. {
  195. if (threadsShouldStop())
  196. {
  197. closeServiceProvider(serviceProvider);
  198. return;
  199. }
  200. synchronized (resumeFlag)
  201. {
  202. if (threadsShouldPause())
  203. {
  204. try
  205. {
  206. resumeFlag.wait();
  207. }
  208. catch (InterruptedException e)
  209. {
  210. myLogger.warn("Dispatcher thread interrupted while waiting to resume");
  211. return;
  212. }
  213. }
  214. }
  215. List<Job> batch = null;
  216. /**
  217. * Includes allocation from capacity. Only returns when the required allocation
  218. * is obtained. Example, RTP port allocation, limit due to total number of allowable calls.
  219. */
  220. ServicePrerequisites prerequisites = null;
  221. synchronized(readyQueue)
  222. {
  223. // get a batch of jobs
  224. Job job = readyQueue.peek();
  225. if (job == null)
  226. try
  227. {
  228. readyQueue.wait();
  229. continue;
  230. }
  231. catch (InterruptedException e)
  232. {
  233. return;
  234. }
  235. prerequisites = secureServicePrerequisites();
  236. if (threadsShouldStop() || threadsShouldPause())
  237. {
  238. returnPrerequisites(prerequisites);
  239. continue;
  240. }
  241. // Now that we can go ahead with this job, let us remove this from queue
  242. readyQueue.poll();
  243. batch = new ArrayList<Job>();
  244. batch.add(job);
  245. // We we are to get a batch of more than one, let us fill in the rest.
  246. for (int i = 1; i < getJobBatchSize(); i++)
  247. {
  248. job = readyQueue.poll();
  249. if (job == null) break;
  250. batch.add(job);
  251. }
  252. }
  253. if (batch != null && batch.size() > 0)
  254. {
  255. // Mark start time
  256. long now = System.currentTimeMillis();
  257. for (Job job : batch)
  258. {
  259. job.startTime = now;
  260. }
  261. // Service the jobs
  262. try
  263. {
  264. processJobs(batch, serviceProvider, prerequisites);
  265. completedJobCount++;
  266. }
  267. catch (EngineException e)
  268. {
  269. terminate(BroadcastState.ABORTED, e.getMessage());
  270. }
  271. }
  272. }
  273. }
  274. }
  275. /**
  276. *
  277. * @param broadcastType
  278. * @param activityRecordIdParamName - if null, default is used.
  279. * @param jobReportRootNodeName
  280. */
  281. protected Broadcast(String broadcastType, String activityRecordIdParamName, String jobReportRootNodeName)
  282. {
  283. this.broadcastType = broadcastType;
  284. this.activityRecordIdParamName = activityRecordIdParamName == null? ACTIVITY_RECORD_ID_PARAM_NAME_DEFAULT : activityRecordIdParamName;
  285. this.jobReportRootNodeName = jobReportRootNodeName;
  286. readyQueue = new LinkedBlockingQueue<Job>();
  287. serviceThreadPool = new ArrayList<Thread>();
  288. recipientList = new ArrayList<Recipient>();
  289. scheduler = Executors.newScheduledThreadPool(SCHEDULER_THREAD_POOL_SIZE);
  290. resumeFlag = new Object();
  291. receiveTime = System.currentTimeMillis();
  292. }
  293. protected abstract void returnPrerequisites(ServicePrerequisites prerequisites);
  294. /**
  295. * Creates and initializes a service provider, to be used by only one service thread.
  296. * If service provider is not thread-specific, then this method may return null, and
  297. * a common service provider is created outside of this method.
  298. *
  299. * @return service provider as a class Object instance.
  300. * @throws BroadcastException
  301. */
  302. protected abstract Object getInitializedServiceProvider() throws BroadcastException;
  303. /**
  304. * Obtains the required components to support a service; e.g. RTP port, or a place
  305. * in maximum total number of calls. Does not return till the reequired prerequisites are obtained.
  306. * @return null, if no prerequisite is required, as in the case of email and sms engines.
  307. */
  308. abstract protected ServicePrerequisites secureServicePrerequisites();
  309. abstract public void closeServiceProvider(Object serviceProvider);
  310. /**
  311. * Makes a state transition to the given newState if the transition from
  312. * the current state is legal.
  313. * @param newState
  314. * @return StateChangeResult
  315. */
  316. public StateChangeResult setState(BroadcastState newState)
  317. {
  318. return setState(newState, null, null);
  319. }
  320. /**
  321. * Makes a state transition to the given newState if the transition from
  322. * the current state is legal.
  323. * @param newState
  324. * @return StateChangeResult
  325. */
  326. public StateChangeResult setState(BroadcastState newState,
  327. String haltReason, String stateErrorText)
  328. {
  329. boolean isLegal;
  330. BroadcastState prev = null;
  331. synchronized (this)
  332. {
  333. if (state == newState) return new StateChangeResult(StateChangeStatus.NO_CHANGE, state, null);
  334. List<BroadcastState> to = toStates.get(state);
  335. isLegal = (to == null? false : to.contains(newState));
  336. prev = state;
  337. if (isLegal)
  338. {
  339. state = newState;
  340. changeStateTime = System.currentTimeMillis();
  341. }
  342. }
  343. if (isLegal)
  344. {
  345. this.haltReason = haltReason;
  346. this.stateErrorText = stateErrorText;
  347. CommonLogger.activity.info(String.format("Broadcast %s: State transitioned from %s to %s", broadcastId, prev, state));
  348. if (postBack != null)
  349. {
  350. postBack.queueReport(mkStatusReport());
  351. }
  352. return new StateChangeResult(StateChangeStatus.SUCCESS, newState, prev);
  353. }
  354. else
  355. {
  356. myLogger.warn(String.format("Broadcast %s: Transition from %s to %s forbidden", broadcastId, prev, newState));
  357. return new StateChangeResult(StateChangeStatus.FORBIDDEN, prev, null);
  358. }
  359. }
  360. protected void setBroadcastId(String broadcastId)
  361. {
  362. if (broadcastId == null)
  363. throw new IllegalArgumentException(
  364. "Argument broadcastId in Broadcast.setBroadcastId method cannot be null");
  365. if (this.broadcastId != null)
  366. throw new IllegalStateException(
  367. "Broadcast.setBroadcastId method cannot be invoked more than once for a Broadcast");
  368. this.broadcastId = broadcastId;
  369. }
  370. protected void setLaunchRecordId(String launchRecordId)
  371. {
  372. if (launchRecordId == null)
  373. throw new IllegalArgumentException(
  374. "Argument launchRecordId in Broadcast.setLaunchRecordId method cannot be null");
  375. if (this.launchRecordId != null)
  376. throw new IllegalStateException(
  377. "Broadcast.setLaunchRecordId method cannot be invoked more than once for a Broadcast");
  378. this.launchRecordId = launchRecordId;
  379. }
  380. public String getBroadcastId()
  381. {
  382. return broadcastId;
  383. }
  384. public String getLaunchRecordId()
  385. {
  386. return launchRecordId;
  387. }
  388. public String getResponseXML(BroadcastException e)
  389. {
  390. String tagName = broadcastType + "_response";
  391. StringBuffer responseXML = new StringBuffer("<" + tagName);
  392. if (broadcastId != null && broadcastId.length() > 0)
  393. {
  394. responseXML.append(" broadcast_id=\"");
  395. responseXML.append(broadcastId);
  396. responseXML.append("\"");
  397. }
  398. responseXML.append(" accepted='");
  399. responseXML.append(e != null || getState() == BroadcastState.COMPLETED ? "FALSE" : "TRUE");
  400. responseXML.append("'");
  401. if (e == null)
  402. {
  403. responseXML.append('>');
  404. }
  405. else
  406. {
  407. if (e.errorCode != null)
  408. {
  409. responseXML.append(" error='");
  410. responseXML.append(e.errorCode.toString());
  411. responseXML.append("'");
  412. }
  413. responseXML.append('>');
  414. if (e.errorText != null)
  415. {
  416. responseXML.append("<error_text>");
  417. responseXML.append(e.errorText);
  418. responseXML.append("</error_text>");
  419. }
  420. }
  421. responseXML.append("</" + tagName + '>');
  422. return responseXML.toString();
  423. }
  424. public String getPostBackURL()
  425. {
  426. return postBackURL;
  427. }
  428. protected String mkResponseXML(String errorCode, String errorText)
  429. {
  430. String tagName = broadcastType + "_response";
  431. StringBuffer responseXML = new StringBuffer("<" + tagName);
  432. String broadcastId = getBroadcastId();
  433. if (broadcastId != null && broadcastId.length() > 0)
  434. {
  435. responseXML.append(" broadcast_id=\"");
  436. responseXML.append(broadcastId);
  437. responseXML.append("\"");
  438. }
  439. responseXML.append(" accepted='");
  440. responseXML.append(errorCode == null ? "TRUE" : "FALSE");
  441. responseXML.append("'");
  442. if (errorCode == null)
  443. {
  444. responseXML.append('>');
  445. }
  446. else
  447. {
  448. responseXML.append(" error='");
  449. responseXML.append(errorCode);
  450. responseXML.append("'");
  451. responseXML.append('>');
  452. if (errorText != null)
  453. {
  454. responseXML.append("<error_text>");
  455. responseXML.append(errorText.replaceAll("\\&", "&amp;")
  456. .replaceAll("<", "&lt;"));
  457. responseXML.append("</error_text>");
  458. }
  459. }
  460. responseXML.append("</" + tagName + '>');
  461. return responseXML.toString();
  462. }
  463. private boolean stateIsFinal(BroadcastState state)
  464. {
  465. return state == BroadcastState.ABORTED || state == BroadcastState.CANCELED
  466. || state == BroadcastState.COMPLETED || state == BroadcastState.PURGED;
  467. }
  468. /**
  469. * If finalState is final, then this state is set, and dispatcher threads are stopped.
  470. * Overriding implementation may release all other resources, like timers.
  471. * @param finalState
  472. */
  473. public void terminate(BroadcastState finalState)
  474. {
  475. terminate(finalState, null);
  476. }
  477. /**
  478. * If finalState is final, then this state is set, and dispatcher threads are stopped.
  479. * Overriding implementation may release all other resources, like timers.
  480. * @param finalState
  481. */
  482. public void terminate(BroadcastState finalState, String reason)
  483. {
  484. if (!stateIsFinal(finalState)) throw new IllegalArgumentException("Argument finalState " + finalState + " in Broadcast.terminate method is not final");
  485. setState(finalState, reason, null);
  486. // Wake up all dispatcher threads waiting on readyQueue so they will all stop
  487. synchronized(readyQueue)
  488. {
  489. readyQueue.notifyAll();
  490. }
  491. // Wake up all sleeping dispatcher threads for same reason.
  492. for(Thread t : serviceThreadPool)
  493. {
  494. try
  495. {
  496. t.interrupt();
  497. }
  498. catch (Exception e)
  499. {
  500. myLogger.warn("Interrupted while waiting for Thread " + t.getName() + " to terminate");
  501. }
  502. }
  503. // Quiesce scheduler, and terminate it.
  504. scheduler.shutdownNow();
  505. }
  506. /**
  507. * Creates status report.
  508. * @return status report in XML.
  509. */
  510. protected String mkStatusReport()
  511. {
  512. StringBuffer statusBf = new StringBuffer();
  513. String topLevelTag = broadcastType;
  514. String broadcastId = getBroadcastId();
  515. if (broadcastId == null) broadcastId = "";
  516. statusBf.append("<" + topLevelTag + " broadcast_id='" + broadcastId
  517. + "' receive_time='" + receiveTime
  518. + "' recipient_count='" + recipientList.size() + "'");
  519. if (launchRecordId != null)
  520. {
  521. statusBf.append(" launch_record_id='" + launchRecordId + "'");
  522. }
  523. statusBf.append(">\r\n<state>" + state + "</state><state_change_time>" + changeStateTime
  524. + "</state_change_time>\r\n");
  525. if (state == BroadcastState.HALTED
  526. || state == BroadcastState.ABORTED)
  527. {
  528. if (haltReason != null)
  529. {
  530. statusBf.append("<reason>" + haltReason
  531. + "</reason>\r\n");
  532. }
  533. if (stateErrorText != null)
  534. {
  535. statusBf.append("<error_text>" + stateErrorText
  536. + "</error_text>");
  537. }
  538. }
  539. statusBf.append("<job_summary completed='" + getCompletedJobCount() +
  540. "' ready='" + getReadyJobCount() + "'");
  541. statusBf.append(" active='" + getActiveJobCount() + "'");
  542. statusBf.append("></job_summary></" + topLevelTag + ">\r\n");
  543. String statusReport = statusBf.toString();
  544. return statusReport;
  545. }
  546. protected void onExpire()
  547. {
  548. }
  549. protected void setExpireTime(long expireTime)
  550. {
  551. this.expireTime = expireTime;
  552. }
  553. public long getExpireTime()
  554. {
  555. return expireTime;
  556. }
  557. /**
  558. *
  559. * @return number of active jobs. -1 if there is no concept of being active.
  560. */
  561. protected int getActiveJobCount()
  562. {
  563. return remainingJobs - readyQueue.size();
  564. }
  565. /**
  566. * Parses broadcastId and return if notInService is true.
  567. * Otherwise, continue parsing postBackUrl, expireTime, recipientList,
  568. * and implementation-specific data from request.
  569. * Avoid throwing an exception before parsing and setting broadcastId.
  570. * @param notInService
  571. * @throws EngineException
  572. */
  573. protected abstract void decode(HttpServletRequest request, boolean notInService)
  574. throws EngineException;
  575. /**
  576. * Remembers postBack, and
  577. * Creates thread pool of size dictated by broadcast, which determines the size based
  578. * on the chosen service provider.
  579. *
  580. * Overriding implementation must invoke this method at the end, and process information
  581. * contained in the broadcast, in preparation for the invocation of the process
  582. * method.
  583. *
  584. * If there is no error, the overriding implementation must return this base method.
  585. *
  586. * @param commEngine
  587. *
  588. * @throws BroadcastException
  589. */
  590. protected final void init(PostBack postBack)
  591. {
  592. // Remember postBack
  593. this.postBack = postBack;
  594. for (Recipient recipient : recipientList)
  595. {
  596. readyQueue.add(mkJob(recipient));
  597. }
  598. remainingJobs = readyQueue.size();
  599. }
  600. protected abstract void initSync(EngineResources resources) throws BroadcastException;
  601. protected Job mkJob(Recipient recipient)
  602. {
  603. return new Job(recipient);
  604. }
  605. /**
  606. * Overriding implementation performs time consuming initialization, after returning
  607. * POST http status indicating accepting broadcast for processing.
  608. *
  609. * @throws BroadcastException
  610. */
  611. protected void initAsync() throws BroadcastException
  612. {
  613. // Do nothing in base class.
  614. }
  615. public String getId()
  616. {
  617. return broadcastId;
  618. }
  619. /**
  620. * Sets the stateMachine to CANCEL
  621. */
  622. protected void cancel()
  623. {
  624. if (this.getActiveJobCount() == 0) setState(BroadcastState.CANCELED);
  625. // Sets state to CANCELING, which is monitored by its Broadcast.Service threads.
  626. else setState(BroadcastState.CANCELING);
  627. synchronized(resumeFlag)
  628. {
  629. resumeFlag.notifyAll();
  630. }
  631. }
  632. protected void pause()
  633. {
  634. // Sets state to HALTED, which is monitored by Broadcast.Service threads.
  635. setState(BroadcastState.HALTING);
  636. }
  637. protected void resume()
  638. {
  639. synchronized (resumeFlag)
  640. {
  641. if (threadsShouldPause())
  642. {
  643. setState(BroadcastState.RUNNING);
  644. resumeFlag.notifyAll();
  645. }
  646. }
  647. }
  648. /**
  649. * Derived class may make its own Implementation of JobReport
  650. * @return
  651. */
  652. protected JobReport mkJobReport()
  653. {
  654. return new JobReport();
  655. }
  656. public void addJob(Job job)
  657. {
  658. synchronized(readyQueue)
  659. {
  660. readyQueue.add(job);
  661. readyQueue.notifyAll();
  662. }
  663. }
  664. public void startProcessing() throws BroadcastException
  665. {
  666. // Create service thread pool to dispatch jobs
  667. for (int i = 0; i < serviceThreadPoolSize; i++)
  668. {
  669. String threadName = broadcastId + "_service_thread_" + i;
  670. Service serviceThread = new Service(threadName);
  671. serviceThreadPool.add(serviceThread);
  672. }
  673. setState(BroadcastState.RUNNING);
  674. // Start the dispatcher threads
  675. for (Thread thread : serviceThreadPool)
  676. {
  677. thread.start();
  678. }
  679. }
  680. private boolean threadsShouldStop()
  681. {
  682. BroadcastState state = getState();
  683. return state == BroadcastState.CANCELING || stateIsFinal(state);
  684. }
  685. private boolean threadsShouldPause()
  686. {
  687. BroadcastState state = getState();
  688. return state == BroadcastState.HALTED || state == BroadcastState.HALTING;
  689. }
  690. /**
  691. * job status is reported back to this broadcast, via the logAndQueueForPostBack method.
  692. * @param batch
  693. * @param prerequisites
  694. */
  695. abstract protected void processJobs(List<Job> batch, Object serviceProvider, ServicePrerequisites prerequisites)
  696. throws EngineException;
  697. /**
  698. * Size of a batch of jobs to be processed together. For email, this may be more than 1,
  699. * and this method should be overridden.
  700. * @return size of a batch of jobs to be processed together.
  701. */
  702. protected int getJobBatchSize()
  703. {
  704. return 1;
  705. }
  706. /**
  707. * Sets jobStatus in job, and post job report.
  708. * If no rescheduling, then decrement number of remainingJobs,
  709. * @param job
  710. * @param jobStatus
  711. * @param errorText
  712. */
  713. public void postJobStatus(Job job)
  714. {
  715. if (postBack != null)
  716. {
  717. JobReport report = mkJobReport();
  718. report.initBase(job, broadcastId, launchRecordId, activityRecordIdParamName, jobReportRootNodeName);
  719. report.init(job);
  720. postBack.queueReport(report.toString());
  721. }
  722. if (job.jobStatus.isTerminal())
  723. {
  724. remainingJobs--;
  725. if (remainingJobs == 0)
  726. {
  727. terminate(BroadcastState.COMPLETED);
  728. }
  729. else if (getActiveJobCount() == 0)
  730. {
  731. if (state == BroadcastState.CANCELING) setState(BroadcastState.CANCELED);
  732. else if (state == BroadcastState.HALTING) setState(BroadcastState.HALTED);
  733. }
  734. }
  735. }
  736. /**
  737. * Sets jobStatus in job, and post job report.
  738. * If no rescheduling, then decrement number of remainingJobs,
  739. * @param job
  740. * @param jobStatus
  741. * @param errorText
  742. * @param reschedule - reschedule time in milliseconds (-1 means do not reschedule).
  743. */
  744. protected void postJobStatus(Job job, long rescheduleTimeMS)
  745. {
  746. postJobStatus(job);
  747. if (rescheduleTimeMS < 0)
  748. {
  749. completedJobCount++;
  750. }
  751. if (rescheduleTimeMS == 0)
  752. {
  753. addJob(job);
  754. }
  755. else if (rescheduleTimeMS > 0)
  756. {
  757. rescheduleJob(job, rescheduleTimeMS);
  758. }
  759. }
  760. public ScheduledFuture<?> rescheduleJob(final Job job, long rescheduleTimeMS)
  761. {
  762. Runnable r = new Runnable() { public void run() { addJob(job);}};
  763. return scheduler.schedule(r, rescheduleTimeMS, TimeUnit.MILLISECONDS);
  764. }
  765. public BroadcastState getState()
  766. {
  767. return state;
  768. }
  769. public int getReadyJobCount()
  770. {
  771. switch (state)
  772. {
  773. case RUNNING:
  774. case HALTING:
  775. case HALTED:
  776. return readyQueue.size();
  777. default:
  778. return 0;
  779. }
  780. }
  781. public int getCompletedJobCount()
  782. {
  783. return completedJobCount;
  784. }
  785. protected void setServiceThreadPoolsize(int serviceThreadPoolSize)
  786. {
  787. this.serviceThreadPoolSize = serviceThreadPoolSize;
  788. }
  789. }