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

459 行
16 KiB

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