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

453 行
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.security.KeyManagementException;
  6. import java.security.KeyStoreException;
  7. import java.security.NoSuchAlgorithmException;
  8. import java.security.cert.X509Certificate;
  9. import java.util.ArrayList;
  10. import java.util.LinkedList;
  11. import java.util.List;
  12. import java.util.Queue;
  13. import javax.net.ssl.SSLContext;
  14. import javax.xml.parsers.DocumentBuilder;
  15. import javax.xml.parsers.DocumentBuilderFactory;
  16. import javax.xml.xpath.XPath;
  17. import javax.xml.xpath.XPathConstants;
  18. import javax.xml.xpath.XPathExpressionException;
  19. import javax.xml.xpath.XPathFactory;
  20. import org.apache.http.StatusLine;
  21. import org.apache.http.client.HttpRequestRetryHandler;
  22. import org.apache.http.client.methods.CloseableHttpResponse;
  23. import org.apache.http.client.methods.HttpPost;
  24. import org.apache.http.conn.ssl.NoopHostnameVerifier;
  25. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  26. import org.apache.http.conn.ssl.TrustStrategy;
  27. import org.apache.http.entity.StringEntity;
  28. import org.apache.http.impl.client.CloseableHttpClient;
  29. import org.apache.http.impl.client.HttpClientBuilder;
  30. import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
  31. import org.apache.http.protocol.BasicHttpContext;
  32. import org.apache.http.protocol.HttpContext;
  33. import org.apache.http.ssl.SSLContextBuilder;
  34. import org.apache.http.util.EntityUtils;
  35. import org.apache.log4j.Logger;
  36. import org.w3c.dom.Document;
  37. import org.w3c.dom.Node;
  38. import altk.comm.engine.CommonLogger;
  39. /**
  40. * Queues JobReports to be posted back to attribute postBackURL.
  41. * Multiple internal class Sender members consume this postQueue, sending items
  42. * in postQueue to postBackURL.
  43. *
  44. * In the future, if postBackURL has problem, or if
  45. * length of postQueue is more than a MAX_QUEUE_LENGTH, then it starts writing
  46. * everything to backingFile.
  47. *
  48. * @author Kwong
  49. *
  50. */
  51. public class PostBack
  52. {
  53. private static final String XML_VERSION_1_0_ENCODING_UTF_8 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
  54. private static final int QUEUE_WAIT = 300; // seconds
  55. private static final int POSTBACK_SERVER_WAIT_TIME = 10; // seconds
  56. private static final int RETRIES_DEFAULT = 3;
  57. private final String postBackURL;
  58. private final String xmlTopElement;
  59. private Queue<String> postQueue;
  60. private final int maxQueueSize;
  61. private List<Sender> senderPool;
  62. private final String myName;
  63. private int maxBatchSize;
  64. private PoolingHttpClientConnectionManager cm;
  65. private int threadsWaitingToPost;
  66. private TrustStrategy tustAllCerts;
  67. SSLContext sslContext;
  68. SSLConnectionSocketFactory connectionFactory;
  69. CloseableHttpClient httpclient;
  70. private int maxRetries;
  71. private static Logger myLogger = Logger.getLogger(PostBack.class);
  72. public enum PostBackStatus
  73. {
  74. SUCCESS,
  75. SERVER_IO_ERROR,
  76. IRRECOVERABLE_ERROR,
  77. HTTP_STATUS_ERROR
  78. }
  79. class Sender extends Thread
  80. {
  81. private boolean threadShouldStop;
  82. private Sender(String name)
  83. {
  84. setName(name);
  85. start();
  86. }
  87. public void run()
  88. {
  89. threadShouldStop = false;
  90. myLogger.info(getName() + " started");
  91. String report;
  92. for (;;) // Each iteration sends a batch
  93. {
  94. if (threadShouldStop)
  95. {
  96. myLogger.info(getName() + " terminating");
  97. System.out.println(getName() + " terminating");
  98. return;
  99. }
  100. myLogger.debug("Looking for reports");
  101. List<String> reportList = null;
  102. synchronized(postQueue)
  103. {
  104. // Each iteration examines the queue for a batch to send
  105. for (;;)
  106. {
  107. reportList = new ArrayList<String>();
  108. for (int i = 0; i < maxBatchSize ; i++)
  109. {
  110. report = postQueue.poll();
  111. if (report == null) break;
  112. reportList.add(report);
  113. }
  114. if (reportList.size() > 0)
  115. {
  116. myLogger.debug(String.format("Extracted %d reports, reducing postQueue size: %d", reportList.size(), postQueue.size()));
  117. postQueue.notifyAll();
  118. break; // break out to do the work.
  119. }
  120. // Nothing to do, so wait a while, and look at the
  121. // queue again.
  122. try
  123. {
  124. myLogger.debug("Going to wait " + QUEUE_WAIT * 1000);
  125. postQueue.wait(QUEUE_WAIT * 1000);
  126. }
  127. catch (InterruptedException e)
  128. {
  129. CommonLogger.alarm.info("Postback queue interrupted while waiting: " + e);
  130. break;
  131. }
  132. CommonLogger.health.info("Surfacing from wait");
  133. System.out.println(getName() + " surfacing from wait");
  134. continue;
  135. }
  136. } // synchronized()
  137. if (reportList != null && reportList.size() > 0)
  138. {
  139. switch (post(reportList))
  140. {
  141. case IRRECOVERABLE_ERROR:
  142. case SUCCESS:
  143. break;
  144. case SERVER_IO_ERROR:
  145. /* Should not requeue report for this may lead to dead lock on this queu.
  146. // TODO: Limit retries, using rate limiting. Posting can be recovered using the activity log.
  147. // Re-queue these reports
  148. for (String rpt : reportList)
  149. {
  150. queueReport(rpt);
  151. }
  152. */
  153. // Sleep for a while before retrying this PostBack server.
  154. CommonLogger.alarm.warn("Caught server IO error. sleep for " + POSTBACK_SERVER_WAIT_TIME + " seconds");
  155. try
  156. {
  157. Thread.sleep(POSTBACK_SERVER_WAIT_TIME * 1000);
  158. }
  159. catch (InterruptedException e)
  160. {
  161. CommonLogger.alarm.warn("Caught while PostBack thread sleeps: " + e);
  162. }
  163. default:
  164. }
  165. }
  166. }
  167. }
  168. /**
  169. *
  170. * @param reportList
  171. * @return SUCCESS,
  172. * SERVER_IO_ERROR, when postback receiver has problem
  173. * IRRECOVERABLE_ERROR
  174. */
  175. private PostBackStatus post(List<String> reportList)
  176. {
  177. StringBuffer xml = new StringBuffer(XML_VERSION_1_0_ENCODING_UTF_8);
  178. xml.append("<"); xml.append(xmlTopElement); xml.append(">");
  179. for (String report : reportList)
  180. {
  181. xml.append(report + "\r\n");
  182. }
  183. xml.append("</"); xml.append(xmlTopElement); xml.append(">");
  184. HttpPost httpPost = new HttpPost(postBackURL);
  185. StringEntity requestEntity;
  186. CloseableHttpResponse response = null;
  187. byte[] xmlBytes = null;
  188. try
  189. {
  190. requestEntity = (new StringEntity(xml.toString()));
  191. httpPost.setEntity(requestEntity);
  192. myLogger.debug("Posting to " + postBackURL + ": " + xml);
  193. response = httpclient.execute(httpPost, new BasicHttpContext());
  194. StatusLine statusLine = response.getStatusLine();
  195. int statusCode = statusLine.getStatusCode();
  196. if (statusCode != 200)
  197. {
  198. CommonLogger.alarm.error("Got error status code " + statusCode + " while posting status to broadcast requester");
  199. return PostBackStatus.HTTP_STATUS_ERROR;
  200. }
  201. }
  202. catch (UnsupportedEncodingException e)
  203. {
  204. CommonLogger.alarm.warn("While adding this application/xml content to PostBack: " + xml + " -- " + e);
  205. return PostBackStatus.IRRECOVERABLE_ERROR;
  206. }
  207. catch (IOException e)
  208. {
  209. CommonLogger.alarm.error("While posting back to broadcast requester: " + e);
  210. return PostBackStatus.IRRECOVERABLE_ERROR;
  211. }
  212. String xmlStr;
  213. try
  214. {
  215. xmlBytes = EntityUtils.toByteArray(response.getEntity());
  216. xmlStr = new String(xmlBytes);
  217. myLogger.debug("Received resposne: " + xmlStr);
  218. }
  219. catch (IOException e)
  220. {
  221. CommonLogger.alarm.error("While getting response from posting to broadcast requester: " + e);
  222. return PostBackStatus.SERVER_IO_ERROR;
  223. }
  224. Document xmlDoc = null;
  225. try
  226. {
  227. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  228. DocumentBuilder builder = factory.newDocumentBuilder();
  229. xmlDoc = builder.parse(new ByteArrayInputStream(xmlBytes));
  230. }
  231. catch (Exception e)
  232. {
  233. CommonLogger.alarm.warn("xml parse problem on received response from " + postBackURL + ": " + xmlStr);
  234. return PostBackStatus.IRRECOVERABLE_ERROR;
  235. }
  236. if (!xmlDoc.getDocumentElement().getNodeName().startsWith(xmlTopElement))
  237. {
  238. CommonLogger.alarm.warn("xml response from " + postBackURL + " not a <" + xmlTopElement + "> response: " + xmlStr);
  239. return PostBackStatus.IRRECOVERABLE_ERROR;
  240. }
  241. XPath xpathEngine = XPathFactory.newInstance().newXPath();
  242. String xpath = null;
  243. try
  244. {
  245. xpath = "@error";
  246. Node errorNode = (Node)xpathEngine.evaluate(xpath, xmlDoc.getDocumentElement(), XPathConstants.NODE);
  247. if (errorNode != null)
  248. {
  249. String errorCode = errorNode.getNodeValue();
  250. xpath = "error_text";
  251. String errorText = (String)xpathEngine.evaluate(xpath,
  252. xmlDoc.getDocumentElement(), XPathConstants.STRING);
  253. CommonLogger.alarm.warn("Error response to <" + xmlTopElement + "> post back to "
  254. + postBackURL + " -- error code=\"" + errorCode + "\", error text = \""
  255. + errorText + "\"");
  256. return PostBackStatus.IRRECOVERABLE_ERROR;
  257. }
  258. }
  259. catch (XPathExpressionException e)
  260. {
  261. CommonLogger.alarm.warn("Bad xpath: " + xpath);
  262. return PostBackStatus.IRRECOVERABLE_ERROR;
  263. }
  264. catch (Exception e)
  265. {
  266. CommonLogger.alarm.warn("While decoding post back response from server: " + e);
  267. return PostBackStatus.IRRECOVERABLE_ERROR;
  268. }
  269. myLogger.debug("returned from posting");
  270. return PostBackStatus.SUCCESS;
  271. }
  272. public void terminate()
  273. {
  274. if (threadShouldStop) return;
  275. threadShouldStop = true;
  276. //Wait for at most 100 ms for thread to stop
  277. interrupt();
  278. }
  279. }
  280. /**
  281. * Constructs a pool of threads doing posting from a common job queue,
  282. * to the supplied postBackURL. The top element of the XML that gets
  283. * posted back has the give name.
  284. *
  285. * Requires these System properties:
  286. * postback_max_queue_size
  287. * postback_threadpool_size
  288. *
  289. * @param postBackURL
  290. * @param xmlTopElementName
  291. * @throws IllegalArgumentException if either postBackURL or xmlTopElementName is
  292. * not supplied nor valid.
  293. * @throws KeyStoreException
  294. * @throws NoSuchAlgorithmException
  295. * @throws KeyManagementException
  296. */
  297. public PostBack(String postBackURL, String xmlTopElementName,
  298. int maxQueueSize, int senderPoolSize, int maxBatchSize)
  299. throws IllegalArgumentException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException
  300. {
  301. if (postBackURL == null || postBackURL.length() == 0)
  302. {
  303. throw new IllegalArgumentException("PostBack class given null postBackURL");
  304. }
  305. myName = "Postback-" + postBackURL;
  306. if (xmlTopElementName == null || xmlTopElementName.length() == 0)
  307. {
  308. throw new IllegalArgumentException(myName + ": PostBack class given null xmlTopElement");
  309. }
  310. this.postBackURL = postBackURL;
  311. this.xmlTopElement = xmlTopElementName;
  312. this.maxQueueSize = maxQueueSize;
  313. this.maxBatchSize = maxBatchSize;
  314. postQueue = new LinkedList<String>();
  315. threadsWaitingToPost = 0;
  316. cm = new PoolingHttpClientConnectionManager();
  317. cm.setMaxTotal(senderPoolSize);
  318. cm.setDefaultMaxPerRoute(senderPoolSize);
  319. tustAllCerts = new TrustStrategy() { public boolean isTrusted(X509Certificate[] chain, String authType) { return true; } };
  320. sslContext = SSLContextBuilder.create().loadTrustMaterial(tustAllCerts).build();
  321. connectionFactory = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
  322. //
  323. // Retry handler
  324. maxRetries = RETRIES_DEFAULT;
  325. HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler()
  326. {
  327. public boolean retryRequest(
  328. IOException exception,
  329. int executionCount,
  330. HttpContext context)
  331. {
  332. if (executionCount >= maxRetries) return false;
  333. return true;
  334. }
  335. };
  336. httpclient = HttpClientBuilder.create()
  337. // .setConnectionManager(cm)
  338. // .setRetryHandler(retryHandler)
  339. .setSSLSocketFactory(connectionFactory)
  340. .build();
  341. senderPool = new ArrayList<Sender>();
  342. for (int i = 0; i < senderPoolSize; i++)
  343. {
  344. Sender sender = new Sender(myName + '-' + i);
  345. senderPool.add(sender);
  346. }
  347. }
  348. /**
  349. * Queues report to postQueue only if the queue size has not reached the
  350. * maxQueueSize.
  351. * @param report
  352. * @return true if report is added to queue, false otherwise (queue full)
  353. */
  354. public boolean queueReport(String report)
  355. {
  356. // Log for recovery in case of problem in posting report.
  357. CommonLogger.activity.info("Attempting to queue report");
  358. synchronized(postQueue)
  359. {
  360. for (;;)
  361. {
  362. if (postQueue.size() < maxQueueSize)
  363. {
  364. myLogger.debug("Queing report" + report);
  365. postQueue.add(report);
  366. myLogger.debug("Added 1 report - postQueue size: " + postQueue.size());
  367. postQueue.notifyAll();
  368. return true;
  369. }
  370. else
  371. {
  372. myLogger.debug("Waiting for space - postQueue size: " + postQueue.size());
  373. try
  374. {
  375. threadsWaitingToPost++;
  376. myLogger.debug("Threads waiting to post: " + threadsWaitingToPost);
  377. postQueue.wait(QUEUE_WAIT * 1000);
  378. }
  379. catch (InterruptedException e)
  380. {
  381. break;
  382. }
  383. threadsWaitingToPost--;
  384. }
  385. }
  386. }
  387. myLogger.info("Interrupted while waiting for space to queue report");
  388. return false;
  389. }
  390. public void terminate()
  391. {
  392. try
  393. {
  394. httpclient.close();
  395. }
  396. catch (IOException e)
  397. {
  398. myLogger.error("Caught when closing HttpClient: " + e.getMessage());
  399. }
  400. // Terminate postback threads
  401. for (Sender sender : senderPool)
  402. {
  403. sender.terminate();
  404. }
  405. }
  406. }