TemplateParser.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. package com.uas.kanban.support;
  2. import com.uas.kanban.annotation.NotEmpty;
  3. import com.uas.kanban.model.GlobalParameter;
  4. import com.uas.kanban.model.Type;
  5. import com.uas.kanban.util.ArrayUtils;
  6. import com.uas.kanban.util.CollectionUtils;
  7. import com.uas.kanban.util.StringUtils;
  8. import me.chyxion.jdbc.NewbieJdbcSupport;
  9. import org.dom4j.Attribute;
  10. import org.dom4j.Document;
  11. import org.dom4j.DocumentException;
  12. import org.dom4j.Element;
  13. import org.dom4j.io.SAXReader;
  14. import org.dom4j.tree.DefaultElement;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import org.springframework.stereotype.Component;
  18. import javax.xml.transform.TransformerException;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.StringReader;
  22. import java.sql.Connection;
  23. import java.sql.PreparedStatement;
  24. import java.sql.ResultSet;
  25. import java.sql.SQLException;
  26. import java.util.*;
  27. import java.util.Map.Entry;
  28. import java.util.regex.Matcher;
  29. import java.util.regex.Pattern;
  30. /**
  31. * @author sunyj
  32. * @since 2017年9月7日 下午2:52:04
  33. */
  34. @Component
  35. public class TemplateParser {
  36. /**
  37. * 查询条件的结果数目限制
  38. */
  39. private static final int MAX_RECORD_SIZE = 10000;
  40. private Logger logger = LoggerFactory.getLogger(getClass());
  41. /**
  42. * 替换模版中的参数为实际值
  43. *
  44. * @param content
  45. * 模版内容
  46. * @param globalParameters
  47. * 公共参数
  48. * @param needSingleQuote
  49. * 参数值是否以单引号括起来(用于 sql)
  50. * @param jdbc
  51. * NewbieJdbcSupport 对象
  52. * @return 替换后的模版内容
  53. * @throws SQLException
  54. */
  55. private String replaceParameters(@NotEmpty("content") String content, List<GlobalParameter> globalParameters,
  56. @NotEmpty("needSingleQuote") Boolean needSingleQuote, @NotEmpty("jdbc") NewbieJdbcSupport jdbc) throws SQLException {
  57. if (CollectionUtils.isEmpty(globalParameters)) {
  58. return content;
  59. }
  60. Map<String, GlobalParameter> map = new HashMap<>();
  61. for (GlobalParameter globalParameter : globalParameters) {
  62. map.put(globalParameter.getName(), globalParameter);
  63. }
  64. String regex = "\\{([\\s\\S]+?)\\}";
  65. Pattern pattern = Pattern.compile(regex);
  66. Matcher matcher = pattern.matcher(content);
  67. while (matcher.find()) {
  68. int start = matcher.start();
  69. int end = matcher.end();
  70. String globalParameterName = matcher.group(1);
  71. GlobalParameter globalParameter = map.get(globalParameterName);
  72. if (globalParameter == null) {
  73. throw new IllegalStateException("未启用公共参数:" + globalParameterName);
  74. }
  75. Object value = globalParameter.getValue();
  76. if (value == null) {
  77. throw new IllegalStateException("公共参数值为null:" + globalParameterName);
  78. }
  79. // 如果公共参数值为 {@link Type#SQL} ,需要取得查询结果
  80. if(globalParameter.getType()== Type.SQL){
  81. String stringValue=(String)value;
  82. // 不递归替换公共参数
  83. // stringValue=replaceParameters(stringValue,globalParameters,true,jdbc);
  84. checkCount(jdbc.getDataSource().getConnection(), stringValue);
  85. Map<String, Object> data = jdbc.findMap(stringValue);
  86. if (CollectionUtils.isEmpty(data)) {
  87. throw new IllegalStateException("公共参数通过 SQL 取得结果为空:"+globalParameterName);
  88. }else if(data.size()>1){
  89. throw new IllegalStateException("公共参数通过 SQL 取得结果不止一列:"+globalParameterName);
  90. }
  91. value=data.values().iterator().next();
  92. }
  93. // TODO value 为时间
  94. if (needSingleQuote != null && needSingleQuote.booleanValue()) {
  95. content = content.substring(0, start) + "'" + value + "'" + content.substring(end);
  96. } else {
  97. content = content.substring(0, start) + value + content.substring(end);
  98. }
  99. matcher = pattern.matcher(content);
  100. }
  101. return content;
  102. }
  103. /**
  104. * 解析单个 xml 模版,转为 json 格式的数据
  105. *
  106. * @param content
  107. * 模版内容
  108. * @param title
  109. * 标题
  110. * @param globalParameters
  111. * 公共参数
  112. * @param jdbc
  113. * NewbieJdbcSupport 对象
  114. * @return 解析后的 json 数据
  115. * @throws DocumentException
  116. * @throws IOException
  117. * @throws TransformerException
  118. * @throws SQLException
  119. * @throws IllegalStateException
  120. */
  121. public String parseXml(@NotEmpty("content") String content, String title,List<GlobalParameter> globalParameters, @NotEmpty("jdbc") NewbieJdbcSupport jdbc)
  122. throws DocumentException, TransformerException, IOException, IllegalStateException, SQLException {
  123. content = processSql(content);
  124. // 替换标题中的公共参数
  125. if (!com.uas.kanban.util.StringUtils.isEmpty(title)) {
  126. title = replaceParameters(title, globalParameters, false,jdbc);
  127. }
  128. content = processForm(content, jdbc, globalParameters);
  129. content = processGrid(content, jdbc, globalParameters);
  130. content = processBarAndLine(content, jdbc, globalParameters);
  131. content = processPie(content, jdbc, globalParameters);
  132. content = finalProcess(content, title);
  133. return TranslateHelper.xmlToJson(content);
  134. }
  135. /**
  136. * 处理 sql ,包括替换特殊字符等
  137. *
  138. * @param content
  139. * 模版内容
  140. * @return 处理后的模版内容
  141. */
  142. public String processSql(@NotEmpty("content") String content) {
  143. String regex = "sql=\"([^\"]+?[<>]+?[^\"]+?)\"";
  144. Pattern pattern = Pattern.compile(regex);
  145. Matcher matcher = pattern.matcher(content);
  146. while (matcher.find()) {
  147. String sql = matcher.group(1);
  148. int start = matcher.start(1);
  149. int end = matcher.end(1);
  150. sql = sql.replaceAll(">", "&gt;").replaceAll("<", "&lt;");
  151. content = content.substring(0, start) + sql + content.substring(end);
  152. matcher = pattern.matcher(content);
  153. }
  154. return content;
  155. }
  156. /**
  157. * 处理模版中的 form 组件
  158. *
  159. * @param content
  160. * 模版内容
  161. * @param jdbc
  162. * NewbieJdbcSupport 对象
  163. * @param globalParameters
  164. * 公共参数
  165. * @return 解析后的模版内容
  166. * @throws DocumentException
  167. * @throws SQLException
  168. * @throws IllegalStateException
  169. */
  170. @SuppressWarnings("unchecked")
  171. private String processForm(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
  172. throws DocumentException, IllegalStateException, SQLException {
  173. Document document = getDocument(content);
  174. // 获取 form 组件
  175. List<Element> elements = document.selectNodes("//form");
  176. for (Element element : elements) {
  177. // 执行 sql ,获取数据
  178. String sql = element.attribute("sql").getText();
  179. if(StringUtils.isEmpty(sql)){
  180. continue;
  181. }
  182. sql = replaceParameters(sql, globalParameters, true,jdbc);
  183. // 获取 field 节点
  184. List<Element> fieldElements = element.elements("field");
  185. if (CollectionUtils.isEmpty(fieldElements)) {
  186. continue;
  187. }
  188. checkCount(jdbc.getDataSource().getConnection(), sql);
  189. Map<String, Object> map = jdbc.findMap(sql);
  190. if (CollectionUtils.isEmpty(map)) {
  191. continue;
  192. }
  193. for (Element fieldElement : fieldElements) {
  194. // 设置 field 的 value 属性
  195. Attribute valueAttribute = fieldElement.attribute("value");
  196. String fieldName = valueAttribute.getText();
  197. valueAttribute.setText(toString(map.get(fieldName.toUpperCase())));
  198. }
  199. }
  200. return document.asXML();
  201. }
  202. /**
  203. * 处理模版中的 grid 组件
  204. *
  205. * @param content
  206. * 模版内容
  207. * @param jdbc
  208. * NewbieJdbcSupport 对象
  209. * @param globalParameters
  210. * 公共参数
  211. * @return 解析后的模版内容
  212. * @throws DocumentException
  213. * @throws SQLException
  214. * @throws IllegalStateException
  215. */
  216. @SuppressWarnings("unchecked")
  217. private String processGrid(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
  218. throws DocumentException, IllegalStateException, SQLException {
  219. Document document = getDocument(content);
  220. // 获取 form 组件
  221. List<Element> elements = document.selectNodes("//grid");
  222. for (Element element : elements) {
  223. // 执行 sql ,获取数据
  224. String sql = element.attribute("sql").getText();
  225. if(StringUtils.isEmpty(sql)){
  226. continue;
  227. }
  228. sql = replaceParameters(sql, globalParameters, true,jdbc);
  229. // 获取 field 节点
  230. List<Element> fieldElements = element.elements("field");
  231. if (CollectionUtils.isEmpty(fieldElements)) {
  232. continue;
  233. }
  234. // 提取 field 名称
  235. List<String> fieldNames = new ArrayList<>();
  236. for (Element fieldElement : fieldElements) {
  237. // 设置 field 的 name
  238. String fieldName = fieldElement.attribute("dataindex").getText();
  239. if (!StringUtils.isEmpty(fieldName)) {
  240. fieldNames.add(fieldName);
  241. }
  242. }
  243. checkCount(jdbc.getDataSource().getConnection(), sql);
  244. List<Map<String, Object>> listMap = jdbc.listMap(sql);
  245. if (CollectionUtils.isEmpty(listMap)) {
  246. continue;
  247. }
  248. for (Map<String, Object> map : listMap) {
  249. // 每一行数据添加一个 data标签
  250. Element dataElement = new DefaultElement("data");
  251. element.add(dataElement);
  252. for (String fieldName : fieldNames) {
  253. // 每一列在 data 下添加一个标签
  254. Element fieldElement = new DefaultElement(fieldName);
  255. fieldElement.setText(toString(map.get(fieldName.toUpperCase())));
  256. dataElement.add(fieldElement);
  257. }
  258. }
  259. }
  260. return document.asXML();
  261. }
  262. /**
  263. * 处理模版中的 bar 和 line 组件
  264. *
  265. * @param content
  266. * 模版内容
  267. * @param jdbc
  268. * NewbieJdbcSupport 对象
  269. * @param globalParameters
  270. * 公共参数
  271. * @return 解析后的模版内容
  272. * @throws DocumentException
  273. * @throws SQLException
  274. * @throws IllegalStateException
  275. */
  276. @SuppressWarnings("unchecked")
  277. private String processBarAndLine(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
  278. throws DocumentException, IllegalStateException, SQLException {
  279. Document document = getDocument(content);
  280. // 获取 bar 和 line 组件
  281. List<Element> elements = document.selectNodes("//bar | //line");
  282. for (Element element : elements) {
  283. // 执行 sql ,获取数据
  284. String sql = element.attribute("sql").getText();
  285. if(StringUtils.isEmpty(sql)){
  286. continue;
  287. }
  288. sql = replaceParameters(sql, globalParameters, true,jdbc);
  289. checkCount(jdbc.getDataSource().getConnection(), sql);
  290. Map<String, List<Object>> map = convert(jdbc.listMap(sql));
  291. if (CollectionUtils.isEmpty(map)) {
  292. continue;
  293. }
  294. // xvalue 字段名
  295. String xvalue = element.attribute("xvalue").getText().toUpperCase();
  296. if (xvalue != null) {
  297. // 设置 xvalue
  298. element.attribute("xfields").setText(toString(map.remove(xvalue)));
  299. }
  300. // fields 名称
  301. String[] fields = element.attribute("fields").getText().split(",[\\s]*");
  302. if (ArrayUtils.isEmpty(fields)) {
  303. continue;
  304. }
  305. // 添加 series 节点,用于存储 fields 的值
  306. for (String field : fields) {
  307. Element seriesElement = new DefaultElement("series");
  308. seriesElement.addAttribute("name", field);
  309. List<Object> value = map.remove(field.toUpperCase());
  310. // 对于某一列,其每一行的数据创建一个 data 标签
  311. if (CollectionUtils.isEmpty(value)) {
  312. Element dataElement = new DefaultElement("data");
  313. seriesElement.add(dataElement);
  314. } else {
  315. for (Object object : value) {
  316. Element dataElement = new DefaultElement("data");
  317. dataElement.setText(toString(object));
  318. seriesElement.add(dataElement);
  319. }
  320. }
  321. element.add(seriesElement);
  322. }
  323. }
  324. return document.asXML();
  325. }
  326. /**
  327. * 处理模版中的pie组件
  328. *
  329. * @param content
  330. * 模版内容
  331. * @param jdbc
  332. * NewbieJdbcSupport 对象
  333. * @param globalParameters
  334. * 公共参数
  335. * @return 解析后的模版内容
  336. * @throws DocumentException
  337. * @throws SQLException
  338. * @throws IllegalStateException
  339. */
  340. @SuppressWarnings("unchecked")
  341. private String processPie(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
  342. throws DocumentException, IllegalStateException, SQLException {
  343. Document document = getDocument(content);
  344. // 获取 bar 和 line 组件
  345. List<Element> elements = document.selectNodes("//pie");
  346. for (Element element : elements) {
  347. // 执行 sql ,获取数据
  348. String sql = element.attribute("sql").getText();
  349. if(StringUtils.isEmpty(sql)){
  350. continue;
  351. }
  352. sql = replaceParameters(sql, globalParameters, true,jdbc);
  353. checkCount(jdbc.getDataSource().getConnection(), sql);
  354. List<Map<String, Object>> listMap = jdbc.listMap(sql);
  355. if (CollectionUtils.isEmpty(listMap)) {
  356. continue;
  357. }
  358. // field 和 value
  359. String field = element.attribute("field").getText().toUpperCase();
  360. String value = element.attribute("value").getText().toUpperCase();
  361. for (Map<String, Object> map : listMap) {
  362. Element seriesElement = new DefaultElement("series");
  363. seriesElement.addAttribute("name", toString(map.get(field)));
  364. seriesElement.addAttribute("data", toString(map.get(value)));
  365. element.add(seriesElement);
  366. }
  367. }
  368. return document.asXML();
  369. }
  370. /**
  371. * 读取 xml 为 Document 对象
  372. *
  373. * @param xml
  374. * xml 字符串
  375. * @return Document 对象
  376. * @throws DocumentException
  377. */
  378. private Document getDocument(@NotEmpty("xml") String xml) throws DocumentException {
  379. return new SAXReader().read(new StringReader(xml));
  380. }
  381. /**
  382. * 检查当前条件下的结果数目是否超出限制
  383. *
  384. * @param connection
  385. * @param sql
  386. * @throws SQLException
  387. * @throws IllegalStateException
  388. */
  389. private void checkCount(@NotEmpty("connection") Connection connection, @NotEmpty("sql") String sql)
  390. throws SQLException, IllegalStateException {
  391. int count = getCount(connection, sql);
  392. if (count > MAX_RECORD_SIZE) {
  393. String message = "查询条件的结果数目超出限制:" + count;
  394. throw new IllegalStateException(message, new IllegalStateException("sql : " + sql));
  395. }
  396. }
  397. /**
  398. * 获取当前查询语句的结果数目
  399. *
  400. * @param connection
  401. * @param sql
  402. * @return
  403. * @throws SQLException
  404. */
  405. private int getCount(@NotEmpty("connection") Connection connection, @NotEmpty("sql") String sql)
  406. throws SQLException {
  407. PreparedStatement preparedStatement = null;
  408. ResultSet resultSet = null;
  409. try {
  410. // 如果直接在 sql 外用 count(1) 统计数目,当关联表多时,可能会出现错误
  411. // ORA-01792: 表或视图中的最大列数为 1000
  412. // 报错主要发生在 select * 的情况下,但是不能这样判断,因为可能存在 select tt.*, pi_id from
  413. // purchase t left join 这样的情况,很难区分
  414. // 因此 1. 对于普通 sql ,将 select 后的字段改为 count(1)
  415. // 2. 而最外层含有 group by 的 sql ,直接改为 count(1) 可能得到不止一行,结果也并非实际行数。再加上
  416. // group
  417. // by 的结果列数一般很小,所以可以在外面使用 count(1) ,一般不会超出 1000 行
  418. String lowerSql = sql.toLowerCase();
  419. if (!lowerSql.matches("[\\s\\S]+?group[\\s]+?by[\\s]+?[^)]+?")) {
  420. String regex = "([\\s\\S]+?from)[\\s]+?[^,]+?";
  421. Pattern pattern = Pattern.compile(regex);
  422. Matcher matcher = pattern.matcher(lowerSql);
  423. if (matcher.find()) {
  424. int start = matcher.start(1);
  425. int end = matcher.end(1);
  426. sql = sql.substring(0, start) + "select count(1) from" + sql.substring(end);
  427. } else {
  428. throw new IllegalStateException("sql 解析错误:未发现第一个 from:" + sql);
  429. }
  430. } else {
  431. sql = "select count(1) from (" + sql + ")";
  432. }
  433. preparedStatement = connection.prepareStatement(sql);
  434. resultSet = preparedStatement.executeQuery();
  435. resultSet.next();
  436. int count = resultSet.getInt(1);
  437. return count;
  438. } finally {
  439. if (resultSet != null) {
  440. try {
  441. resultSet.close();
  442. } catch (SQLException e) {
  443. logger.error("", e);
  444. }
  445. }
  446. if (preparedStatement != null) {
  447. try {
  448. preparedStatement.close();
  449. } catch (SQLException e) {
  450. logger.error("", e);
  451. }
  452. }
  453. connection.close();
  454. }
  455. }
  456. /**
  457. * 将 List<Map<String, Object>> 转为 Map<String, List<Object>>
  458. *
  459. * @param listMap
  460. * 将转换的对象,每个 Map 的键都相同,否则会得到非预期的结果<br/>
  461. * 如下所示:
  462. * <p/>
  463. * <table border=1 cellpadding=5 cellspacing=0 summary=
  464. * "Key and value">
  465. * <tr>
  466. * <th>输入</th>
  467. * <th>输出</th>
  468. * <th>结果</th>
  469. * </tr>
  470. * <tr>
  471. * <td>[{age=12, name=Joe}, {age=31, name=Lee}]</td>
  472. * <td>{name=[Joe, Lee], age=[12, 31]}</td>
  473. * <td>预期</td>
  474. * </tr>
  475. * <tr>
  476. * <td>[{age=12, name=Joe}, {name=Lee, agee=31}]</td>
  477. * <td>{name=[Joe, Lee], age=[12], agee=[31]}</td>
  478. * <td>非预期</td>
  479. * </tr>
  480. * </table>
  481. * @return 转换的结果
  482. */
  483. private Map<String, List<Object>> convert(List<Map<String, Object>> listMap) {
  484. Map<String, List<Object>> result = new HashMap<>();
  485. if (CollectionUtils.isEmpty(listMap)) {
  486. return result;
  487. }
  488. for (Map<String, Object> map : listMap) {
  489. Set<Entry<String, Object>> entrySet = map.entrySet();
  490. for (Entry<String, Object> entry : entrySet) {
  491. String key = entry.getKey();
  492. Object value = entry.getValue();
  493. List<Object> list = result.get(key);
  494. if (list == null) {
  495. list = new ArrayList<>();
  496. result.put(key, list);
  497. }
  498. list.add(value);
  499. }
  500. }
  501. return result;
  502. }
  503. /**
  504. * 将对象转为字符串
  505. *
  506. * @param object
  507. * 对象
  508. * @return 转换的字符串
  509. */
  510. private String toString(Object object) {
  511. if (object == null) {
  512. return "";
  513. }
  514. if (object instanceof Date) {
  515. return String.valueOf(((Date) object).getTime());
  516. }
  517. return String.valueOf(object);
  518. }
  519. /**
  520. * 对模版内容的最终处理,包括映射、添加title等
  521. *
  522. * @param content
  523. * 模版内容
  524. * @param title
  525. * 标题
  526. * @return 处理后的模版内容
  527. * @throws IOException
  528. * @throws TransformerException
  529. * @throws DocumentException
  530. */
  531. private String finalProcess(@NotEmpty("content") String content, String title)
  532. throws TransformerException, IOException, DocumentException {
  533. InputStream mapRuleStream = FileHelper.readStream("map-rule.xsl");
  534. content = TranslateHelper.map(content, mapRuleStream);
  535. if (!StringUtils.isEmpty(title)) {
  536. content = addTitle(content, title);
  537. }
  538. return content;
  539. }
  540. /**
  541. * 为模版添加 title
  542. *
  543. * @param content
  544. * 模版内容
  545. * @param title
  546. * 标题
  547. * @return 处理后的模版内容
  548. * @throws DocumentException
  549. */
  550. private String addTitle(@NotEmpty("content") String content, @NotEmpty("title") String title)
  551. throws DocumentException {
  552. Document document = getDocument(content);
  553. Element rootElement = document.getRootElement();
  554. Element titleElement = new DefaultElement("title");
  555. titleElement.setText(title);
  556. rootElement.add(titleElement);
  557. return document.asXML();
  558. }
  559. }