Imported from dev1.link2tek.net CommEngine.git
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

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