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

455 строки
17 KiB

  1. package altk.comm.engine.postback;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.IOException;
  4. import java.io.UnsupportedEncodingException;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import java.util.LinkedList;
  8. import java.util.List;
  9. import java.util.Queue;
  10. import javax.xml.parsers.DocumentBuilder;
  11. import javax.xml.parsers.DocumentBuilderFactory;
  12. import javax.xml.xpath.XPath;
  13. import javax.xml.xpath.XPathConstants;
  14. import javax.xml.xpath.XPathExpressionException;
  15. import javax.xml.xpath.XPathFactory;
  16. import org.apache.commons.httpclient.ConnectTimeoutException;
  17. import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
  18. import org.apache.commons.httpclient.Header;
  19. import org.apache.commons.httpclient.HttpClient;
  20. import org.apache.commons.httpclient.HttpURL;
  21. import org.apache.commons.httpclient.methods.PostMethod;
  22. import org.apache.commons.httpclient.methods.StringRequestEntity;
  23. import org.apache.commons.httpclient.params.HttpMethodParams;
  24. import org.apache.log4j.Logger;
  25. import org.w3c.dom.Document;
  26. import org.w3c.dom.Node;
  27. import altk.comm.engine.CommonLogger;
  28. /**
  29. * Queues JobReports to be posted back to attribute postBackURL.
  30. * Multiple internal class Sender members consume this postQueue, sending items
  31. * in postQueue to postBackURL.
  32. *
  33. * In the future, if postBackURL has problem, or if
  34. * length of postQueue is more than a MAX_QUEUE_LENGTH, then it starts writing
  35. * everything to backingFile.
  36. *
  37. * @author Kwong
  38. *
  39. */
  40. public class PostBack
  41. {
  42. private static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  43. private static final int QUEUE_WAIT = 300; // seconds
  44. private static final int POSTBACK_SERVER_WAIT_TIME = 10; // seconds
  45. private final String postBackURL;
  46. private final String xmlTopElement;
  47. private Queue<String> postQueue;
  48. private final int maxQueueSize;
  49. private List<Sender> senderPool;
  50. private final String myName;
  51. private int maxBatchSize;
  52. private static Logger myLogger = Logger.getLogger(PostBack.class);
  53. public enum PostBackStatus
  54. {
  55. SUCCESS,
  56. SERVER_IO_ERROR,
  57. IRRECOVERABLE_ERROR,
  58. HTTP_STATUS_ERROR
  59. }
  60. class Sender extends Thread
  61. {
  62. private boolean threadShouldStop;
  63. private Sender(String name)
  64. {
  65. setName(name);
  66. start();
  67. }
  68. public void run()
  69. {
  70. threadShouldStop = false;
  71. myLogger.info(getName() + " started");
  72. String report;
  73. for (;;) // Each iteration sends a batch
  74. {
  75. if (threadShouldStop)
  76. {
  77. myLogger.info(getName() + " terminating");
  78. System.out.println(getName() + " terminating");
  79. return;
  80. }
  81. List<String> reportList = null;
  82. synchronized(postQueue)
  83. {
  84. // Each iteration examines the queue for a batch to send
  85. for (;;)
  86. {
  87. reportList = new ArrayList<String>();
  88. for (int i = 0; i < maxBatchSize ; i++)
  89. {
  90. report = postQueue.poll();
  91. if (report == null) break;
  92. reportList.add(report);
  93. }
  94. if (reportList.size() > 0)
  95. {
  96. postQueue.notify();
  97. break; // break out to do the work.
  98. }
  99. // Nothing to do, so wait a while, and look at the
  100. // queue again.
  101. try
  102. {
  103. postQueue.wait(QUEUE_WAIT * 1000);
  104. }
  105. catch (InterruptedException e)
  106. {
  107. CommonLogger.alarm.info(getName() + ": Postback queue interrupted while waiting: " + e);
  108. break;
  109. }
  110. CommonLogger.health.info(getName() + " surfacing from wait");
  111. System.out.println(getName() + " surfacing from wait");
  112. continue;
  113. }
  114. } // synchronized()
  115. if (reportList != null && reportList.size() > 0)
  116. {
  117. switch (post(reportList))
  118. {
  119. case IRRECOVERABLE_ERROR:
  120. case SUCCESS:
  121. break;
  122. case SERVER_IO_ERROR:
  123. // TODO: Limit retries, using rate limiting. Posting can be recovered using the activity log.
  124. // Re-queue these reports
  125. for (String rpt : reportList)
  126. {
  127. queueReport(rpt);
  128. }
  129. // Sleep for a while before retrying this PostBack server.
  130. CommonLogger.alarm.warn(getName() + ": Caught server IO error. sleep for " + POSTBACK_SERVER_WAIT_TIME + " seconds");
  131. try
  132. {
  133. Thread.sleep(POSTBACK_SERVER_WAIT_TIME * 1000);
  134. }
  135. catch (InterruptedException e)
  136. {
  137. CommonLogger.alarm.warn(getName() + ": Caught while PostBack thread sleeps: " + e);
  138. }
  139. default:
  140. }
  141. }
  142. }
  143. }
  144. /**
  145. *
  146. * @param reportList
  147. * @return SUCCESS,
  148. * SERVER_IO_ERROR, when postback receiver has problem
  149. * IRRECOVERABLE_ERROR
  150. */
  151. private PostBackStatus post(List<String> reportList)
  152. {
  153. StringBuffer xml = new StringBuffer(XML_VERSION_1_0_ENCODING_UTF_8);
  154. xml.append("<"); xml.append(xmlTopElement); xml.append(">");
  155. for (String report : reportList)
  156. {
  157. xml.append(report);
  158. }
  159. xml.append("</"); xml.append(xmlTopElement); xml.append(">");
  160. PostMethod post = new PostMethod();
  161. String responseBody = null;
  162. StringRequestEntity requestEntity = null;
  163. try
  164. {
  165. requestEntity = new StringRequestEntity(xml.toString(), "application/xml", "utf-8");
  166. }
  167. catch (UnsupportedEncodingException e)
  168. {
  169. CommonLogger.alarm.warn(getName() + ": While adding this application/xml content to PostBack: " + xml + " -- " + e);
  170. return PostBackStatus.IRRECOVERABLE_ERROR;
  171. }
  172. post.setRequestEntity(requestEntity);
  173. post.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
  174. new DefaultHttpMethodRetryHandler(3, false));
  175. /*
  176. CommonLogger.activity.debug("Before calling setFolloweRedirects()");
  177. post.setFollowRedirects(false);
  178. CommonLogger.activity.debug("After calling setFolloweRedirects()");
  179. */
  180. HttpClient client = new HttpClient();
  181. String url = postBackURL;
  182. for (int redirectCount = 0; redirectCount < 3; redirectCount++)
  183. {
  184. try
  185. {
  186. client.getHttpConnectionManager().getParams().setConnectionTimeout(5 * 1000);
  187. post.setURI(new HttpURL(url));
  188. if (redirectCount == 0)
  189. {
  190. CommonLogger.activity.info(getName() + ": posting " + xml.toString() + " to " + url);
  191. }
  192. else
  193. {
  194. CommonLogger.activity.info(getName() + ": redirected to " + url);
  195. }
  196. int statusCode = client.executeMethod(post);
  197. if (statusCode == 302)
  198. {
  199. Header locationHeader = post.getResponseHeader("Location");
  200. if (locationHeader != null)
  201. {
  202. url = locationHeader.getValue();
  203. post.releaseConnection();
  204. continue;
  205. }
  206. else
  207. {
  208. CommonLogger.alarm.warn(getName() + ": When posting to \"" + url + "\": " + " received status 302 but without location header");
  209. return PostBackStatus.IRRECOVERABLE_ERROR;
  210. }
  211. }
  212. else if (statusCode != 200)
  213. {
  214. CommonLogger.alarm.warn(getName() + ": Received problem status code " + statusCode + " from posting to \"" + url + "\": " + xml);
  215. return PostBackStatus.HTTP_STATUS_ERROR;
  216. }
  217. responseBody = post.getResponseBodyAsString().trim();
  218. post.releaseConnection();
  219. CommonLogger.activity.info(getName() + ": Received response: " + (responseBody.length() == 0? "[empty]" : responseBody));
  220. if (responseBody.trim().length() == 0) return PostBackStatus.SUCCESS;
  221. break;
  222. }
  223. catch (ConnectTimeoutException e)
  224. {
  225. CommonLogger.alarm.warn(getName() + ": IO problem while posting to \"" + url + "\": " + xml + " -- " + e.getMessage());
  226. return PostBackStatus.SERVER_IO_ERROR;
  227. }
  228. catch (IOException e)
  229. {
  230. CommonLogger.alarm.warn(getName() + ": IO problem while posting to \"" + url + "\": " + xml + " -- " + e.getMessage());
  231. return PostBackStatus.SERVER_IO_ERROR;
  232. }
  233. catch (IllegalArgumentException e)
  234. {
  235. CommonLogger.alarm.warn(getName() + ": When posting to \"" + url + "\": " + e.getMessage());
  236. return PostBackStatus.IRRECOVERABLE_ERROR;
  237. }
  238. }
  239. if (responseBody == null)
  240. {
  241. CommonLogger.alarm.warn(getName() + ": When posting to \"" + url + "\": " + " Exhausted allowable redirects");
  242. return PostBackStatus.IRRECOVERABLE_ERROR;
  243. }
  244. // parse into xml doc
  245. Document xmlDoc = null;
  246. try
  247. {
  248. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  249. DocumentBuilder builder = factory.newDocumentBuilder();
  250. xmlDoc = builder.parse(new ByteArrayInputStream(responseBody.getBytes()));
  251. }
  252. catch (Exception e)
  253. {
  254. CommonLogger.alarm.warn(getName() + ": xml parse problem on received response from " + postBackURL + ": " + responseBody);
  255. return PostBackStatus.IRRECOVERABLE_ERROR;
  256. }
  257. if (!xmlDoc.getDocumentElement().getNodeName().startsWith(xmlTopElement))
  258. {
  259. CommonLogger.alarm.warn(getName() + ": xml response from " + postBackURL + " not a <" + xmlTopElement + "> response: " + responseBody);
  260. return PostBackStatus.IRRECOVERABLE_ERROR;
  261. }
  262. XPath xpathEngine = XPathFactory.newInstance().newXPath();
  263. String xpath = null;
  264. try
  265. {
  266. xpath = "@error";
  267. Node errorNode = (Node)xpathEngine.evaluate(xpath, xmlDoc.getDocumentElement(), XPathConstants.NODE);
  268. if (errorNode != null)
  269. {
  270. String errorCode = errorNode.getNodeValue();
  271. xpath = "error_text";
  272. String errorText = (String)xpathEngine.evaluate(xpath,
  273. xmlDoc.getDocumentElement(), XPathConstants.STRING);
  274. CommonLogger.alarm.warn(getName() + ": Error response to <" + xmlTopElement + "> post back to "
  275. + postBackURL + " -- error code=\"" + errorCode + "\", error text = \""
  276. + errorText + "\"");
  277. return PostBackStatus.IRRECOVERABLE_ERROR;
  278. }
  279. }
  280. catch (XPathExpressionException e)
  281. {
  282. CommonLogger.alarm.warn("Bad xpath: " + xpath);
  283. return PostBackStatus.IRRECOVERABLE_ERROR;
  284. }
  285. catch (Exception e)
  286. {
  287. CommonLogger.alarm.warn(getName() + ": While decoding post back response from server: " + e);
  288. return PostBackStatus.IRRECOVERABLE_ERROR;
  289. }
  290. return PostBackStatus.SUCCESS;
  291. }
  292. public void terminate()
  293. {
  294. if (threadShouldStop) return;
  295. threadShouldStop = true;
  296. //Wait for at most 100 ms for thread to stop
  297. interrupt();
  298. }
  299. }
  300. /**
  301. * Constructs a pool of threads doing posting from a common job queue,
  302. * to the supplied postBackURL. The top element of the XML that gets
  303. * posted back has the give name.
  304. *
  305. * Requires these System properties:
  306. * postback_max_queue_size
  307. * postback_threadpool_size
  308. *
  309. * @param postBackURL
  310. * @param xmlTopElementName
  311. * @throws IllegalArgumentException if either postBackURL or xmlTopElementName is
  312. * not supplied nor valid.
  313. */
  314. public PostBack(String postBackURL, String xmlTopElementName,
  315. int maxQueueSize, int senderPoolSize, int maxBatchSize)
  316. throws IllegalArgumentException
  317. {
  318. if (postBackURL == null || postBackURL.length() == 0)
  319. {
  320. throw new IllegalArgumentException("PostBack class given null postBackURL");
  321. }
  322. myName = "Postback-" + postBackURL;
  323. if (xmlTopElementName == null || xmlTopElementName.length() == 0)
  324. {
  325. throw new IllegalArgumentException(myName + ": PostBack class given null xmlTopElement");
  326. }
  327. this.postBackURL = postBackURL;
  328. this.xmlTopElement = xmlTopElementName;
  329. this.maxQueueSize = maxQueueSize;
  330. this.maxBatchSize = maxBatchSize;
  331. postQueue = new LinkedList<String>();
  332. senderPool = new ArrayList<Sender>();
  333. for (int i = 0; i < senderPoolSize; i++)
  334. {
  335. Sender sender = new Sender(myName + '-' + i);
  336. senderPool.add(sender);
  337. }
  338. }
  339. /**
  340. * Queues report to postQueue only if the queue size has not reached the
  341. * maxQueueSize.
  342. * @param report
  343. * @return true if report is added to queue, false otherwise (queue full)
  344. */
  345. public boolean queueReport(String report)
  346. {
  347. // Log for recovery in case of problem in posting report.
  348. CommonLogger.activity.info(myName + " queing report: " + report);
  349. myLogger.debug(myName + ": postQueue size: " + postQueue.size());
  350. synchronized(postQueue)
  351. {
  352. for (;;)
  353. {
  354. if (postQueue.size() < maxQueueSize)
  355. {
  356. postQueue.add(report);
  357. postQueue.notify();
  358. return true;
  359. }
  360. else
  361. {
  362. myLogger.debug("Waiting for space in postQueue to queue report");
  363. try
  364. {
  365. postQueue.wait();
  366. }
  367. catch (InterruptedException e)
  368. {
  369. break;
  370. }
  371. }
  372. }
  373. }
  374. myLogger.error("Interrupted while waiting for space to queue report");
  375. return false;
  376. }
  377. /**
  378. * Queues reports to postQueue only if the queue size has not reached the
  379. * maxQueueSize.
  380. * @param reports to be added back to postQueue
  381. * @return true if all jobs have been added to queue, false otherwise (queue full)
  382. */
  383. /*
  384. @Deprecated
  385. public boolean queueReports(List<String> reports)
  386. {
  387. myLogger.debug(myName + ": postQueue size: " + postQueue.size());
  388. synchronized(postQueue)
  389. {
  390. Iterator<String> iter = reports.iterator();
  391. int count = 0; // Number of reports added back to postQueue
  392. while (iter.hasNext())
  393. {
  394. String report = iter.next();
  395. if (postQueue.size() < maxQueueSize)
  396. {
  397. postQueue.add(report);
  398. count++;
  399. }
  400. }
  401. if (count > 0) postQueue.notify();
  402. boolean returnValue = (count == reports.size());
  403. if (!returnValue)
  404. {
  405. CommonLogger.alarm.warn(myName
  406. + ".queueReport method returning false, having queued "
  407. + count + " out of " + reports.size());
  408. }
  409. return returnValue;
  410. }
  411. }
  412. */
  413. public void terminate()
  414. {
  415. for (Sender sender : senderPool)
  416. {
  417. sender.terminate();
  418. }
  419. }
  420. }