| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582 |
- package com.uas.kanban.support;
- import com.uas.kanban.annotation.NotEmpty;
- import com.uas.kanban.model.GlobalParameter;
- import com.uas.kanban.model.Type;
- import com.uas.kanban.util.ArrayUtils;
- import com.uas.kanban.util.CollectionUtils;
- import com.uas.kanban.util.StringUtils;
- import me.chyxion.jdbc.NewbieJdbcSupport;
- import org.dom4j.Attribute;
- import org.dom4j.Document;
- import org.dom4j.DocumentException;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- import org.dom4j.tree.DefaultElement;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
- import javax.xml.transform.TransformerException;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.StringReader;
- import java.sql.Connection;
- import java.sql.PreparedStatement;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.util.*;
- import java.util.Map.Entry;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * @author sunyj
- * @since 2017年9月7日 下午2:52:04
- */
- @Component
- public class TemplateParser {
- /**
- * 查询条件的结果数目限制
- */
- private static final int MAX_RECORD_SIZE = 10000;
- private Logger logger = LoggerFactory.getLogger(getClass());
- /**
- * 替换模版中的参数为实际值
- *
- * @param content
- * 模版内容
- * @param globalParameters
- * 公共参数
- * @param needSingleQuote
- * 参数值是否以单引号括起来(用于 sql)
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @return 替换后的模版内容
- * @throws SQLException
- */
- private String replaceParameters(@NotEmpty("content") String content, List<GlobalParameter> globalParameters,
- @NotEmpty("needSingleQuote") Boolean needSingleQuote, @NotEmpty("jdbc") NewbieJdbcSupport jdbc) throws SQLException {
- if (CollectionUtils.isEmpty(globalParameters)) {
- return content;
- }
- Map<String, GlobalParameter> map = new HashMap<>();
- for (GlobalParameter globalParameter : globalParameters) {
- map.put(globalParameter.getName(), globalParameter);
- }
- String regex = "\\{([\\s\\S]+?)\\}";
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(content);
- while (matcher.find()) {
- int start = matcher.start();
- int end = matcher.end();
- String globalParameterName = matcher.group(1);
- GlobalParameter globalParameter = map.get(globalParameterName);
- if (globalParameter == null) {
- throw new IllegalStateException("未启用公共参数:" + globalParameterName);
- }
- Object value = globalParameter.getValue();
- if (value == null) {
- throw new IllegalStateException("公共参数值为null:" + globalParameterName);
- }
- // 如果公共参数值为 {@link Type#SQL} ,需要取得查询结果
- if(globalParameter.getType()== Type.SQL){
- String stringValue=(String)value;
- // 不递归替换公共参数
- // stringValue=replaceParameters(stringValue,globalParameters,true,jdbc);
- checkCount(jdbc.getDataSource().getConnection(), stringValue);
- Map<String, Object> data = jdbc.findMap(stringValue);
- if (CollectionUtils.isEmpty(data)) {
- throw new IllegalStateException("公共参数通过 SQL 取得结果为空:"+globalParameterName);
- }else if(data.size()>1){
- throw new IllegalStateException("公共参数通过 SQL 取得结果不止一列:"+globalParameterName);
- }
- value=data.values().iterator().next();
- }
- // TODO value 为时间
- if (needSingleQuote != null && needSingleQuote.booleanValue()) {
- content = content.substring(0, start) + "'" + value + "'" + content.substring(end);
- } else {
- content = content.substring(0, start) + value + content.substring(end);
- }
- matcher = pattern.matcher(content);
- }
- return content;
- }
- /**
- * 解析单个 xml 模版,转为 json 格式的数据
- *
- * @param content
- * 模版内容
- * @param title
- * 标题
- * @param globalParameters
- * 公共参数
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @return 解析后的 json 数据
- * @throws DocumentException
- * @throws IOException
- * @throws TransformerException
- * @throws SQLException
- * @throws IllegalStateException
- */
- public String parseXml(@NotEmpty("content") String content, String title,List<GlobalParameter> globalParameters, @NotEmpty("jdbc") NewbieJdbcSupport jdbc)
- throws DocumentException, TransformerException, IOException, IllegalStateException, SQLException {
- content = processSql(content);
- // 替换标题中的公共参数
- if (!com.uas.kanban.util.StringUtils.isEmpty(title)) {
- title = replaceParameters(title, globalParameters, false,jdbc);
- }
- content = processForm(content, jdbc, globalParameters);
- content = processGrid(content, jdbc, globalParameters);
- content = processBarAndLine(content, jdbc, globalParameters);
- content = processPie(content, jdbc, globalParameters);
- content = finalProcess(content, title);
- return TranslateHelper.xmlToJson(content);
- }
- /**
- * 处理 sql ,包括替换特殊字符等
- *
- * @param content
- * 模版内容
- * @return 处理后的模版内容
- */
- public String processSql(@NotEmpty("content") String content) {
- String regex = "sql=\"([^\"]+?[<>]+?[^\"]+?)\"";
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(content);
- while (matcher.find()) {
- String sql = matcher.group(1);
- int start = matcher.start(1);
- int end = matcher.end(1);
- sql = sql.replaceAll(">", ">").replaceAll("<", "<");
- content = content.substring(0, start) + sql + content.substring(end);
- matcher = pattern.matcher(content);
- }
- return content;
- }
- /**
- * 处理模版中的 form 组件
- *
- * @param content
- * 模版内容
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @param globalParameters
- * 公共参数
- * @return 解析后的模版内容
- * @throws DocumentException
- * @throws SQLException
- * @throws IllegalStateException
- */
- @SuppressWarnings("unchecked")
- private String processForm(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
- throws DocumentException, IllegalStateException, SQLException {
- Document document = getDocument(content);
- // 获取 form 组件
- List<Element> elements = document.selectNodes("//form");
- for (Element element : elements) {
- // 执行 sql ,获取数据
- String sql = element.attribute("sql").getText();
- if(StringUtils.isEmpty(sql)){
- continue;
- }
- sql = replaceParameters(sql, globalParameters, true,jdbc);
- // 获取 field 节点
- List<Element> fieldElements = element.elements("field");
- if (CollectionUtils.isEmpty(fieldElements)) {
- continue;
- }
- checkCount(jdbc.getDataSource().getConnection(), sql);
- Map<String, Object> map = jdbc.findMap(sql);
- if (CollectionUtils.isEmpty(map)) {
- continue;
- }
- for (Element fieldElement : fieldElements) {
- // 设置 field 的 value 属性
- Attribute valueAttribute = fieldElement.attribute("value");
- String fieldName = valueAttribute.getText();
- valueAttribute.setText(toString(map.get(fieldName.toUpperCase())));
- }
- }
- return document.asXML();
- }
- /**
- * 处理模版中的 grid 组件
- *
- * @param content
- * 模版内容
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @param globalParameters
- * 公共参数
- * @return 解析后的模版内容
- * @throws DocumentException
- * @throws SQLException
- * @throws IllegalStateException
- */
- @SuppressWarnings("unchecked")
- private String processGrid(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
- throws DocumentException, IllegalStateException, SQLException {
- Document document = getDocument(content);
- // 获取 form 组件
- List<Element> elements = document.selectNodes("//grid");
- for (Element element : elements) {
- // 执行 sql ,获取数据
- String sql = element.attribute("sql").getText();
- if(StringUtils.isEmpty(sql)){
- continue;
- }
- sql = replaceParameters(sql, globalParameters, true,jdbc);
- // 获取 field 节点
- List<Element> fieldElements = element.elements("field");
- if (CollectionUtils.isEmpty(fieldElements)) {
- continue;
- }
- // 提取 field 名称
- List<String> fieldNames = new ArrayList<>();
- for (Element fieldElement : fieldElements) {
- // 设置 field 的 name
- String fieldName = fieldElement.attribute("dataindex").getText();
- if (!StringUtils.isEmpty(fieldName)) {
- fieldNames.add(fieldName);
- }
- }
- checkCount(jdbc.getDataSource().getConnection(), sql);
- List<Map<String, Object>> listMap = jdbc.listMap(sql);
- if (CollectionUtils.isEmpty(listMap)) {
- continue;
- }
- for (Map<String, Object> map : listMap) {
- // 每一行数据添加一个 data标签
- Element dataElement = new DefaultElement("data");
- element.add(dataElement);
- for (String fieldName : fieldNames) {
- // 每一列在 data 下添加一个标签
- Element fieldElement = new DefaultElement(fieldName);
- fieldElement.setText(toString(map.get(fieldName.toUpperCase())));
- dataElement.add(fieldElement);
- }
- }
- }
- return document.asXML();
- }
- /**
- * 处理模版中的 bar 和 line 组件
- *
- * @param content
- * 模版内容
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @param globalParameters
- * 公共参数
- * @return 解析后的模版内容
- * @throws DocumentException
- * @throws SQLException
- * @throws IllegalStateException
- */
- @SuppressWarnings("unchecked")
- private String processBarAndLine(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
- throws DocumentException, IllegalStateException, SQLException {
- Document document = getDocument(content);
- // 获取 bar 和 line 组件
- List<Element> elements = document.selectNodes("//bar | //line");
- for (Element element : elements) {
- // 执行 sql ,获取数据
- String sql = element.attribute("sql").getText();
- if(StringUtils.isEmpty(sql)){
- continue;
- }
- sql = replaceParameters(sql, globalParameters, true,jdbc);
- checkCount(jdbc.getDataSource().getConnection(), sql);
- Map<String, List<Object>> map = convert(jdbc.listMap(sql));
- if (CollectionUtils.isEmpty(map)) {
- continue;
- }
- // xvalue 字段名
- String xvalue = element.attribute("xvalue").getText().toUpperCase();
- if (xvalue != null) {
- // 设置 xvalue
- element.attribute("xfields").setText(toString(map.remove(xvalue)));
- }
- // fields 名称
- String[] fields = element.attribute("fields").getText().split(",[\\s]*");
- if (ArrayUtils.isEmpty(fields)) {
- continue;
- }
- // 添加 series 节点,用于存储 fields 的值
- for (String field : fields) {
- Element seriesElement = new DefaultElement("series");
- seriesElement.addAttribute("name", field);
- List<Object> value = map.remove(field.toUpperCase());
- // 对于某一列,其每一行的数据创建一个 data 标签
- if (CollectionUtils.isEmpty(value)) {
- Element dataElement = new DefaultElement("data");
- seriesElement.add(dataElement);
- } else {
- for (Object object : value) {
- Element dataElement = new DefaultElement("data");
- dataElement.setText(toString(object));
- seriesElement.add(dataElement);
- }
- }
- element.add(seriesElement);
- }
- }
- return document.asXML();
- }
- /**
- * 处理模版中的pie组件
- *
- * @param content
- * 模版内容
- * @param jdbc
- * NewbieJdbcSupport 对象
- * @param globalParameters
- * 公共参数
- * @return 解析后的模版内容
- * @throws DocumentException
- * @throws SQLException
- * @throws IllegalStateException
- */
- @SuppressWarnings("unchecked")
- private String processPie(@NotEmpty("content") String content, @NotEmpty("jdbc") NewbieJdbcSupport jdbc, List<GlobalParameter> globalParameters)
- throws DocumentException, IllegalStateException, SQLException {
- Document document = getDocument(content);
- // 获取 bar 和 line 组件
- List<Element> elements = document.selectNodes("//pie");
- for (Element element : elements) {
- // 执行 sql ,获取数据
- String sql = element.attribute("sql").getText();
- if(StringUtils.isEmpty(sql)){
- continue;
- }
- sql = replaceParameters(sql, globalParameters, true,jdbc);
- checkCount(jdbc.getDataSource().getConnection(), sql);
- List<Map<String, Object>> listMap = jdbc.listMap(sql);
- if (CollectionUtils.isEmpty(listMap)) {
- continue;
- }
- // field 和 value
- String field = element.attribute("field").getText().toUpperCase();
- String value = element.attribute("value").getText().toUpperCase();
- for (Map<String, Object> map : listMap) {
- Element seriesElement = new DefaultElement("series");
- seriesElement.addAttribute("name", toString(map.get(field)));
- seriesElement.addAttribute("data", toString(map.get(value)));
- element.add(seriesElement);
- }
- }
- return document.asXML();
- }
- /**
- * 读取 xml 为 Document 对象
- *
- * @param xml
- * xml 字符串
- * @return Document 对象
- * @throws DocumentException
- */
- private Document getDocument(@NotEmpty("xml") String xml) throws DocumentException {
- return new SAXReader().read(new StringReader(xml));
- }
- /**
- * 检查当前条件下的结果数目是否超出限制
- *
- * @param connection
- * @param sql
- * @throws SQLException
- * @throws IllegalStateException
- */
- private void checkCount(@NotEmpty("connection") Connection connection, @NotEmpty("sql") String sql)
- throws SQLException, IllegalStateException {
- int count = getCount(connection, sql);
- if (count > MAX_RECORD_SIZE) {
- String message = "查询条件的结果数目超出限制:" + count;
- throw new IllegalStateException(message, new IllegalStateException("sql : " + sql));
- }
- }
- /**
- * 获取当前查询语句的结果数目
- *
- * @param connection
- * @param sql
- * @return
- * @throws SQLException
- */
- private int getCount(@NotEmpty("connection") Connection connection, @NotEmpty("sql") String sql)
- throws SQLException {
- PreparedStatement preparedStatement = null;
- ResultSet resultSet = null;
- try {
- // 如果直接在 sql 外用 count(1) 统计数目,当关联表多时,可能会出现错误
- // ORA-01792: 表或视图中的最大列数为 1000
- // 报错主要发生在 select * 的情况下,但是不能这样判断,因为可能存在 select tt.*, pi_id from
- // purchase t left join 这样的情况,很难区分
- // 因此 1. 对于普通 sql ,将 select 后的字段改为 count(1)
- // 2. 而最外层含有 group by 的 sql ,直接改为 count(1) 可能得到不止一行,结果也并非实际行数。再加上
- // group
- // by 的结果列数一般很小,所以可以在外面使用 count(1) ,一般不会超出 1000 行
- String lowerSql = sql.toLowerCase();
- if (!lowerSql.matches("[\\s\\S]+?group[\\s]+?by[\\s]+?[^)]+?")) {
- String regex = "([\\s\\S]+?from)[\\s]+?[^,]+?";
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(lowerSql);
- if (matcher.find()) {
- int start = matcher.start(1);
- int end = matcher.end(1);
- sql = sql.substring(0, start) + "select count(1) from" + sql.substring(end);
- } else {
- throw new IllegalStateException("sql 解析错误:未发现第一个 from:" + sql);
- }
- } else {
- sql = "select count(1) from (" + sql + ")";
- }
- preparedStatement = connection.prepareStatement(sql);
- resultSet = preparedStatement.executeQuery();
- resultSet.next();
- int count = resultSet.getInt(1);
- return count;
- } finally {
- if (resultSet != null) {
- try {
- resultSet.close();
- } catch (SQLException e) {
- logger.error("", e);
- }
- }
- if (preparedStatement != null) {
- try {
- preparedStatement.close();
- } catch (SQLException e) {
- logger.error("", e);
- }
- }
- connection.close();
- }
- }
- /**
- * 将 List<Map<String, Object>> 转为 Map<String, List<Object>>
- *
- * @param listMap
- * 将转换的对象,每个 Map 的键都相同,否则会得到非预期的结果<br/>
- * 如下所示:
- * <p/>
- * <table border=1 cellpadding=5 cellspacing=0 summary=
- * "Key and value">
- * <tr>
- * <th>输入</th>
- * <th>输出</th>
- * <th>结果</th>
- * </tr>
- * <tr>
- * <td>[{age=12, name=Joe}, {age=31, name=Lee}]</td>
- * <td>{name=[Joe, Lee], age=[12, 31]}</td>
- * <td>预期</td>
- * </tr>
- * <tr>
- * <td>[{age=12, name=Joe}, {name=Lee, agee=31}]</td>
- * <td>{name=[Joe, Lee], age=[12], agee=[31]}</td>
- * <td>非预期</td>
- * </tr>
- * </table>
- * @return 转换的结果
- */
- private Map<String, List<Object>> convert(List<Map<String, Object>> listMap) {
- Map<String, List<Object>> result = new HashMap<>();
- if (CollectionUtils.isEmpty(listMap)) {
- return result;
- }
- for (Map<String, Object> map : listMap) {
- Set<Entry<String, Object>> entrySet = map.entrySet();
- for (Entry<String, Object> entry : entrySet) {
- String key = entry.getKey();
- Object value = entry.getValue();
- List<Object> list = result.get(key);
- if (list == null) {
- list = new ArrayList<>();
- result.put(key, list);
- }
- list.add(value);
- }
- }
- return result;
- }
- /**
- * 将对象转为字符串
- *
- * @param object
- * 对象
- * @return 转换的字符串
- */
- private String toString(Object object) {
- if (object == null) {
- return "";
- }
- if (object instanceof Date) {
- return String.valueOf(((Date) object).getTime());
- }
- return String.valueOf(object);
- }
- /**
- * 对模版内容的最终处理,包括映射、添加title等
- *
- * @param content
- * 模版内容
- * @param title
- * 标题
- * @return 处理后的模版内容
- * @throws IOException
- * @throws TransformerException
- * @throws DocumentException
- */
- private String finalProcess(@NotEmpty("content") String content, String title)
- throws TransformerException, IOException, DocumentException {
- InputStream mapRuleStream = FileHelper.readStream("map-rule.xsl");
- content = TranslateHelper.map(content, mapRuleStream);
- if (!StringUtils.isEmpty(title)) {
- content = addTitle(content, title);
- }
- return content;
- }
- /**
- * 为模版添加 title
- *
- * @param content
- * 模版内容
- * @param title
- * 标题
- * @return 处理后的模版内容
- * @throws DocumentException
- */
- private String addTitle(@NotEmpty("content") String content, @NotEmpty("title") String title)
- throws DocumentException {
- Document document = getDocument(content);
- Element rootElement = document.getRootElement();
- Element titleElement = new DefaultElement("title");
- titleElement.setText(title);
- rootElement.add(titleElement);
- return document.asXML();
- }
- }
|