From 85215cedf5cb0c14e36dba633fc6dc0ad3cc661e Mon Sep 17 00:00:00 2001 From: "Jonguk. Lim" Date: Sun, 26 Jun 2022 20:43:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20sql=20parser=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 +- .../ctgy/v2/service/impl/BoardService.java | 12 +- .../core/oauth2/api/dao/RefreshTokenDao.java | 32 +++-- .../support/sql/parser/QueryGenerator.java | 112 +++++++++++++++ .../sql/parser/parameter/ParameterList.java | 94 ++++++++++++ .../sql/parser/parameter/ParameterMap.java | 39 +++++ .../IllegalParameterIndexException.java | 9 ++ .../exceptions/NoMarkValueFoundException.java | 15 ++ .../NoParameterFoundForMarkException.java | 15 ++ .../sql/parser/sqlNodes/AbstractSqlNode.java | 121 ++++++++++++++++ .../sql/parser/sqlNodes/BasicSqlNode.java | 16 +++ .../sql/parser/sqlNodes/CaseSqlNode.java | 18 +++ .../sql/parser/sqlNodes/IfSqlNode.java | 21 +++ .../sqlNodes/InvalidTagNameException.java | 8 ++ .../sql/parser/sqlNodes/OtherwiseSqlNode.java | 16 +++ .../parser/sqlNodes/SimpleSqlNodeFactory.java | 37 +++++ .../support/sql/parser/sqlNodes/SqlNode.java | 14 ++ .../sql/parser/sqlNodes/SwitchSqlNode.java | 38 +++++ .../sql/parser/sqlNodes/WhereSqlNode.java | 55 +++++++ .../sqlNodes/expression/ExpressionUtils.java | 135 ++++++++++++++++++ .../InvalidExpressionException.java | 8 ++ .../parser/sqlNodes/expression/Operator.java | 17 +++ .../sql/parser/sqlQuery/BasicSqlQuery.java | 114 +++++++++++++++ .../sql/parser/sqlQuery/EmptySqlQuery.java | 56 ++++++++ .../support/sql/parser/sqlQuery/SqlQuery.java | 24 ++++ src/main/resources/sql/board-mapper.xml | 31 ++++ src/main/resources/sql/board2-mapper.xml | 53 ------- .../resources/sql/refreshToken-mapper.xml | 27 ++-- src/main/resources/sqlMapping.xml | 7 + 29 files changed, 1074 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/xit/core/support/sql/parser/QueryGenerator.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/parameter/ParameterList.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/parameter/ParameterMap.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/IllegalParameterIndexException.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoMarkValueFoundException.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoParameterFoundForMarkException.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/AbstractSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/BasicSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/CaseSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/IfSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/InvalidTagNameException.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/OtherwiseSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/SimpleSqlNodeFactory.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/SqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/SwitchSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/WhereSqlNode.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/ExpressionUtils.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/InvalidExpressionException.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/Operator.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlQuery/BasicSqlQuery.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlQuery/EmptySqlQuery.java create mode 100644 src/main/java/com/xit/core/support/sql/parser/sqlQuery/SqlQuery.java create mode 100644 src/main/resources/sql/board-mapper.xml delete mode 100644 src/main/resources/sql/board2-mapper.xml create mode 100644 src/main/resources/sqlMapping.xml diff --git a/build.gradle b/build.gradle index 29fc5aa..c213ace 100644 --- a/build.gradle +++ b/build.gradle @@ -169,8 +169,12 @@ dependencies { runtimeOnly 'com.h2database:h2:1.4.199' // 2.0.202 사용시 에러 발생 implementation 'org.postgresql:postgresql:42.3.1' - // https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils - implementation group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.4' + + //-----------------------------------------------------------------------------------// + // sql parser lib + //-----------------------------------------------------------------------------------// + implementation 'dom4j:dom4j:1.6.1' + implementation 'jaxen:jaxen:1.2.0' //-----------------------------------------------------------------------------------// diff --git a/src/main/java/com/xit/biz/ctgy/v2/service/impl/BoardService.java b/src/main/java/com/xit/biz/ctgy/v2/service/impl/BoardService.java index b29ac39..f236e12 100644 --- a/src/main/java/com/xit/biz/ctgy/v2/service/impl/BoardService.java +++ b/src/main/java/com/xit/biz/ctgy/v2/service/impl/BoardService.java @@ -12,6 +12,7 @@ import com.xit.core.constant.ErrorCode; import com.xit.core.exception.CustomBaseException; import com.xit.core.oauth2.utils.HeaderUtil; import com.xit.core.support.jpa.JpaUtil; +import com.xit.core.support.sql.parser.QueryGenerator; import com.xit.core.util.Checks; import com.xit.core.util.CommUtil; import com.xit.core.util.DBUtils; @@ -24,6 +25,7 @@ import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; +import org.dom4j.DocumentException; import org.mapstruct.factory.Mappers; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.data.domain.Example; @@ -50,9 +52,17 @@ public class BoardService implements IBoardService { @Transactional(readOnly = true) public List findAll(final BoardDto dto, Pageable pageable) { - String sql = DBUtils.getXmlSql("sql/board2-mapper", "selectBoardList"); + //String sql = DBUtils.getXmlSql("sql/board2-mapper", "selectBoardList"); //String sql = DBUtils.getMybatisSql(sqlSessionTemplate, "board.selectBoardList", dto); + + String sql = QueryGenerator.createNamedQuery("board", "selectBoardList") + .setParameter("ciTitle", dto.getCiTitle()) + .setParameter("ciName", dto.getCiName()) + .setParameter("ciContents", dto.getCiContents()) + .getQueryString(); + System.out.println(sql); + MpowerUtils sendXml = new MpowerUtils(); sendXml.setFeilds("ciCode, ciName, ciContentno, ciTitle, ciContents, ciNalja, ciStep, ciRevel, ciRef, ciHit, ciPass, ciId"); sendXml.setQuery(sql); diff --git a/src/main/java/com/xit/core/oauth2/api/dao/RefreshTokenDao.java b/src/main/java/com/xit/core/oauth2/api/dao/RefreshTokenDao.java index a512152..6480036 100644 --- a/src/main/java/com/xit/core/oauth2/api/dao/RefreshTokenDao.java +++ b/src/main/java/com/xit/core/oauth2/api/dao/RefreshTokenDao.java @@ -1,6 +1,7 @@ package com.xit.core.oauth2.api.dao; import com.xit.core.oauth2.api.entity.RefreshToken; +import com.xit.core.support.sql.parser.QueryGenerator; import com.xit.core.util.DBUtils; import com.xit.core.util.mpower.MpowerUtils; import org.springframework.stereotype.Repository; @@ -13,8 +14,11 @@ public class RefreshTokenDao { private static final String sqlXmlFile = "sql/refreshToken-mapper"; public Optional findByKey(String key){ - String sql = DBUtils.getXmlSql(sqlXmlFile, "selectRefreshToken"); - sql = sql.replaceFirst(":userId", key); + String sql = QueryGenerator.createNamedQuery("refreshToken", "selectRefreshToken") + .setParameter("key", key) + .getQueryString(); + //String sql = DBUtils.getXmlSql(sqlXmlFile, "selectRefreshToken"); + //sql = sql.replaceFirst(":userId", key); MpowerUtils sendXml = new MpowerUtils(); sendXml.setFeilds("key, value"); @@ -23,9 +27,15 @@ public class RefreshTokenDao { } public void save(RefreshToken refreshToken){ - String sql = DBUtils.getXmlSql(sqlXmlFile, "saveRefreshToken"); - sql = sql.replaceFirst(":userId", refreshToken.getKey()); - sql = sql.replaceFirst(":tokenValue", refreshToken.getValue()); +// String sql = DBUtils.getXmlSql(sqlXmlFile, "saveRefreshToken"); +// sql = sql.replaceFirst(":userId", refreshToken.getKey()); +// sql = sql.replaceFirst(":tokenValue", refreshToken.getValue()); + + String sql = QueryGenerator.createNamedQuery("refreshToken", "insertRefreshToken") + .setParameter("key", refreshToken.getKey()) + .setParameter("value", refreshToken.getValue()) + .getQueryString(); + MpowerUtils sendXml = new MpowerUtils(); //sendXml.setFeilds("key, value"); @@ -34,9 +44,15 @@ public class RefreshTokenDao { } public void update(RefreshToken refreshToken){ - String sql = DBUtils.getXmlSql(sqlXmlFile, "updateRefreshToken"); - sql = sql.replaceFirst(":userId", refreshToken.getKey()); - sql = sql.replaceFirst(":tokenValue", refreshToken.getValue()); +// String sql = DBUtils.getXmlSql(sqlXmlFile, "updateRefreshToken"); +// sql = sql.replaceFirst(":userId", refreshToken.getKey()); +// sql = sql.replaceFirst(":tokenValue", refreshToken.getValue()); + + String sql = QueryGenerator.createNamedQuery("refreshToken", "updateRefreshToken") + .setParameter("value", refreshToken.getValue()) + .setParameter("key", refreshToken.getKey()) + .getQueryString(); + MpowerUtils sendXml = new MpowerUtils(); //sendXml.setFeilds("key, value"); diff --git a/src/main/java/com/xit/core/support/sql/parser/QueryGenerator.java b/src/main/java/com/xit/core/support/sql/parser/QueryGenerator.java new file mode 100644 index 0000000..258e48b --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/QueryGenerator.java @@ -0,0 +1,112 @@ +package com.xit.core.support.sql.parser; + +import com.xit.core.support.sql.parser.sqlQuery.BasicSqlQuery; +import com.xit.core.support.sql.parser.sqlQuery.EmptySqlQuery; +import com.xit.core.support.sql.parser.sqlQuery.SqlQuery; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Element; +import org.dom4j.io.SAXReader; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * lib use + * implementation 'dom4j:dom4j:1.6.1' + * implementation 'jaxen:jaxen:1.2.0' + */ +public class QueryGenerator { + + private static volatile Map classNameRootElementMap; + + private static final String ROOT_PATH = System.getProperty("user.dir"); + + private QueryGenerator() { + } + + public static SqlQuery createNamedQuery(String className, String queryName) { + Element rootElement = getElementFromMapping(className); + if (rootElement == null || rootElement.isTextOnly()) { + return new EmptySqlQuery(); + } + Element element = findElementWithQueryName(queryName, rootElement); + return element == null ? new EmptySqlQuery() : new BasicSqlQuery(element); + } + + private static void initializeConfig() { + synchronized (QueryGenerator.class) { + if (classNameRootElementMap == null) { + classNameRootElementMap = new HashMap<>(); + File confFile = new File(ROOT_PATH + "/src/main/resources/sqlMapping.xml"); + SAXReader saxReader = new SAXReader(); + Document document = null; + try { + document = saxReader.read(confFile); + } catch (DocumentException e) { + throw new RuntimeException(e); + } + Element rootElement = document.getRootElement(); + Iterator iterator = rootElement.elementIterator(); + while (iterator.hasNext()) { + Element element = (Element) iterator.next(); + String tagName = element.getName(); + String path = ROOT_PATH + element.attributeValue("path"); + if (tagName.equals("file-location") || tagName.equals("directory-location")) { + loadMappingFile(new File(path)); + } else { + throw new RuntimeException("Invalid tag name, could only be 'file-location' or 'directory-location'"); + } + } + } + } + } + + private static void loadMappingFile(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File mappingFile : files) { + loadMappingFile(mappingFile); + } + } + return; + } + SAXReader saxReader = new SAXReader(); + Document document = null; + try { + document = saxReader.read(file); + } catch (DocumentException e) { + throw new RuntimeException(e); + } + Element rootElement = document.getRootElement(); + String nameSpace = rootElement.attributeValue("namespace"); + if (nameSpace == null || nameSpace.isEmpty()) { + throw new RuntimeException("namespace for config file " + file.getName() + " cannot be empty"); + } + classNameRootElementMap.put(nameSpace, rootElement); + } + + private static Element getElementFromMapping(String className) { + if (classNameRootElementMap == null) { + initializeConfig(); + } + return classNameRootElementMap.get(className); + } + + private static Element findElementWithQueryName(String queryName, Element rootElement) { + Iterator iterator = rootElement.elementIterator(); + while (iterator.hasNext()) { + Element element = (Element) iterator.next(); + if (element.getName().equals("named-native-query")) { + String name = element.attributeValue("name"); + if (name != null && name.trim().equals(queryName.trim())) { + return element; + } + } + } + return null; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterList.java b/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterList.java new file mode 100644 index 0000000..3f192f0 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterList.java @@ -0,0 +1,94 @@ +package com.xit.core.support.sql.parser.parameter; +import com.xit.core.support.sql.parser.parameter.exceptions.IllegalParameterIndexException; +import com.xit.core.support.sql.parser.parameter.exceptions.NoParameterFoundForMarkException; + +class ParameterList { + + private int curIndex; + + ParamNode dummyHead; + + ParamNode dummayTail; + + private int numOfNodes; + + ParameterList(){ + this.curIndex = 1; + this.dummyHead = new ParamNode(0, null); + this.dummayTail = new ParamNode(Integer.MAX_VALUE, null); + this.dummyHead.next = this.dummayTail; + this.dummayTail.prev = this.dummyHead; + } + + + public void put(int position, String value){ + if (position <= 0 || position == Integer.MAX_VALUE) { + throw new IllegalParameterIndexException(); + } + ParamNode cur = dummyHead; + while (cur.index < position) { + cur = cur.next; + } + if (cur.index == position) { + cur.value = value; + } else { + ParamNode newNode = new ParamNode(position, value); + newNode.prev = cur.prev; + newNode.next = cur; + cur.prev.next = newNode; + cur.prev = newNode; + } + numOfNodes++; + } + + public String peek() { + if (isEmpty()) { + return null; + } + return dummyHead.next.index == curIndex ? dummyHead.next.value : null; + } + + public String poll() { + if (isEmpty()) { + throw new NoParameterFoundForMarkException(curIndex); + } + if (dummyHead.next.index == curIndex) { + String value = dummyHead.next.value; + remove(dummyHead.next); + curIndex++; + return value; + } else { + throw new NoParameterFoundForMarkException(curIndex); + } + } + + private void remove(ParamNode paramNode) { + ParamNode prev = paramNode.prev; + ParamNode next = paramNode.next; + prev.next = next; + next.prev = prev; + this.numOfNodes--; + } + + + public boolean isEmpty() { + return numOfNodes == 0; + } + + + static class ParamNode{ + + int index; + + String value; + + ParamNode next; + + ParamNode prev; + + ParamNode(int index, String value){ + this.index = index; + this.value = value; + } + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterMap.java b/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterMap.java new file mode 100644 index 0000000..1ad727d --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/parameter/ParameterMap.java @@ -0,0 +1,39 @@ +package com.xit.core.support.sql.parser.parameter; +import java.util.HashMap; +import java.util.Map; + +public class ParameterMap { + + private Map map; + + private ParameterList parameterList; + + public ParameterMap(){ + this.map = new HashMap<>(); + this.parameterList = new ParameterList(); + } + + public void put(int index, String value){ + this.parameterList.put(index, value); + } + + public void put(String key, String value) { + this.map.put(key, value); + } + + public String get(String key) { + return map.get(key); + } + + public boolean containsKey(String key) { + return map.containsKey(key); + } + + public String poll() { + return parameterList.poll(); + } + + public String peek() { + return parameterList.peek(); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/IllegalParameterIndexException.java b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/IllegalParameterIndexException.java new file mode 100644 index 0000000..3684897 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/IllegalParameterIndexException.java @@ -0,0 +1,9 @@ +package com.xit.core.support.sql.parser.parameter.exceptions; + +public class IllegalParameterIndexException extends RuntimeException { + + @Override + public String getMessage() { + return "The Index for the parameter should range from 1 to " + Integer.MAX_VALUE; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoMarkValueFoundException.java b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoMarkValueFoundException.java new file mode 100644 index 0000000..c855792 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoMarkValueFoundException.java @@ -0,0 +1,15 @@ +package com.xit.core.support.sql.parser.parameter.exceptions; + +public class NoMarkValueFoundException extends RuntimeException { + + String key; + + public NoMarkValueFoundException(String key) { + this.key = key; + } + + @Override + public String getMessage(){ + return "No value found for key " + key; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoParameterFoundForMarkException.java b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoParameterFoundForMarkException.java new file mode 100644 index 0000000..db3a2cd --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/parameter/exceptions/NoParameterFoundForMarkException.java @@ -0,0 +1,15 @@ +package com.xit.core.support.sql.parser.parameter.exceptions; + +public class NoParameterFoundForMarkException extends RuntimeException { + + private int index; + + public NoParameterFoundForMarkException(int index) { + this.index = index; + } + + @Override + public String getMessage() { + return "No parameter set for the '?' mark on index " + index; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/AbstractSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/AbstractSqlNode.java new file mode 100644 index 0000000..eb7d702 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/AbstractSqlNode.java @@ -0,0 +1,121 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import com.xit.core.support.sql.parser.parameter.exceptions.NoMarkValueFoundException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.Node; +import org.dom4j.XPath; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class AbstractSqlNode implements SqlNode { + + /** + * Regular expression to find the marks which are used to set the parameters + * There are three types of marks: #{value}, (:List), ? + */ + public static final String REGEXPS = "#\\{[^}]*}|\\(:[^)]*\\)|\\?"; + + protected Element element; + + protected ParameterMap parameterMap; + + public AbstractSqlNode(Element element, ParameterMap parameterMap) { + this.element = element; + this.parameterMap = parameterMap; + } + + @Override + public String parseText(String text) { + text = preProcessText(text); + Pattern pattern = Pattern.compile(REGEXPS); + Matcher matcher = pattern.matcher(text); + StringBuffer stringBuffer = new StringBuffer(); + while (matcher.find()) { + String key = getKeyFromMatchedPattern(matcher.group()); + if (key == null) { + matcher.appendReplacement(stringBuffer, ""); + } else { + if (key.equals("?")) { + matcher.appendReplacement(stringBuffer, parameterMap.poll()); + } else { + if (!parameterMap.containsKey(key)) { + throw new NoMarkValueFoundException(key); + } + matcher.appendReplacement(stringBuffer, parameterMap.get(key)); + } + } + } + matcher.appendTail(stringBuffer); + return stringBuffer.toString(); + } + + private List filterEmptyNodeList(Listnodes) { + ListnodeList = new LinkedList<>(); + if (nodes == null) { + return nodeList; + } + for (Node node : nodes) { + if (node.getName() != null) { + nodeList.add(node); + } else { + String text = node.getText(); + if (text == null) { + continue; + } + text = text.replaceAll(System.lineSeparator(), ""); + if (!text.trim().isEmpty()) { + nodeList.add(node); + } + } + } + return nodeList; + } + + @Override + public String getFullText() { + if (element.isTextOnly()) { + return parseText(element.getText()); + } + StringBuilder stringBuilder = new StringBuilder(); + XPath xPath = DocumentHelper.createXPath("./node()"); + List nodeList = filterEmptyNodeList(xPath.selectNodes(element)); + for (Node node : nodeList) { + if (node.getName() != null) { + SqlNode sqlNode = SimpleSqlNodeFactory.getSqlNode((Element)node, parameterMap); + if (sqlNode.display()) { + stringBuilder.append(System.lineSeparator()).append(sqlNode.getFullText()); + } + } else { + stringBuilder.append(parseText(node.getText())); + } + } + return stringBuilder.toString(); + } + + private String preProcessText(String text) { + text.replaceAll(System.lineSeparator(), " "); + text = text.trim(); + return text + " "; + } + + + private String getKeyFromMatchedPattern(String matchedPattern) { + if (matchedPattern == null || matchedPattern.isEmpty()) { + return null; + } + switch (matchedPattern.charAt(0)) { + case '#': + case '(': { + return matchedPattern.substring(2, matchedPattern.length() - 1); + } + case '?': { + return "?"; + } + } + return null; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/BasicSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/BasicSqlNode.java new file mode 100644 index 0000000..aaaa5a0 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/BasicSqlNode.java @@ -0,0 +1,16 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import org.dom4j.Element; + +public class BasicSqlNode extends AbstractSqlNode { + + public BasicSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public boolean display() { + return true; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/CaseSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/CaseSqlNode.java new file mode 100644 index 0000000..3a1b013 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/CaseSqlNode.java @@ -0,0 +1,18 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import com.xit.core.support.sql.parser.sqlNodes.expression.ExpressionUtils; +import org.dom4j.Element; + +public class CaseSqlNode extends AbstractSqlNode { + + + public CaseSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public boolean display() { + return ExpressionUtils.isTrueExpression(this.element.attributeValue("text"), parameterMap); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/IfSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/IfSqlNode.java new file mode 100644 index 0000000..504d99b --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/IfSqlNode.java @@ -0,0 +1,21 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import com.xit.core.support.sql.parser.sqlNodes.expression.ExpressionUtils; +import org.dom4j.Element; + +public class IfSqlNode extends AbstractSqlNode { + + public IfSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public boolean display() { + String expression = element.attributeValue("text"); + if (expression == null || expression.trim().isEmpty()) { + return false; + } + return ExpressionUtils.isTrueExpression(this.element.attributeValue("text"), parameterMap); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/InvalidTagNameException.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/InvalidTagNameException.java new file mode 100644 index 0000000..2930992 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/InvalidTagNameException.java @@ -0,0 +1,8 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +public class InvalidTagNameException extends RuntimeException { + + public InvalidTagNameException(String message) { + super(message); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/OtherwiseSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/OtherwiseSqlNode.java new file mode 100644 index 0000000..d86a124 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/OtherwiseSqlNode.java @@ -0,0 +1,16 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import org.dom4j.Element; + +public class OtherwiseSqlNode extends AbstractSqlNode { + + public OtherwiseSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public boolean display() { + return true; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SimpleSqlNodeFactory.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SimpleSqlNodeFactory.java new file mode 100644 index 0000000..065dd2f --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SimpleSqlNodeFactory.java @@ -0,0 +1,37 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import org.dom4j.Element; + +public class SimpleSqlNodeFactory { + + public static SqlNode getSqlNode(Element element, ParameterMap parameterMap) { + if (element == null) { + return null; + } + SqlNode result; + switch (element.getName().toLowerCase()) { + case "if": + result = new IfSqlNode(element, parameterMap); + break; + case "switch": + result = new SwitchSqlNode(element, parameterMap); + break; + case "case": + result = new CaseSqlNode(element, parameterMap); + break; + case "where": + result = new WhereSqlNode(element, parameterMap); + break; + case "otherwise": + result = new OtherwiseSqlNode(element, parameterMap); + break; + case "named-native-query": + result = new BasicSqlNode(element, parameterMap); + break; + default: + throw new InvalidTagNameException("Invalid tag name: " + element.getName()); + } + return result; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SqlNode.java new file mode 100644 index 0000000..64fa77e --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SqlNode.java @@ -0,0 +1,14 @@ +package com.xit.core.support.sql.parser.sqlNodes; + + +/** + * Basic interface for each tag in xml + */ +public interface SqlNode { + + String parseText(String text); + + String getFullText(); + + boolean display(); +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SwitchSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SwitchSqlNode.java new file mode 100644 index 0000000..9cdb7c6 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/SwitchSqlNode.java @@ -0,0 +1,38 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import org.dom4j.Element; + +import java.util.Iterator; + +public class SwitchSqlNode extends AbstractSqlNode { + + public SwitchSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public String getFullText() { + StringBuilder stringBuilder = new StringBuilder(); + if (element.isTextOnly()) { + return stringBuilder.toString(); + } + Iterator iterator = element.elementIterator(); + while (iterator.hasNext()) { + SqlNode sqlNode = SimpleSqlNodeFactory.getSqlNode((Element) iterator.next(), parameterMap); + if (!(sqlNode instanceof CaseSqlNode) && !(sqlNode instanceof OtherwiseSqlNode)) { + throw new RuntimeException("Invalid xml tag inside switch tag, must be 'case' or 'otherwise'"); + } + if (sqlNode.display()) { + stringBuilder.append(sqlNode.getFullText()).append(System.lineSeparator()); + break; + } + } + return stringBuilder.toString(); + } + + @Override + public boolean display() { + return true; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/WhereSqlNode.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/WhereSqlNode.java new file mode 100644 index 0000000..a0e0f84 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/WhereSqlNode.java @@ -0,0 +1,55 @@ +package com.xit.core.support.sql.parser.sqlNodes; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import org.dom4j.Element; + +import java.util.Iterator; + +public class WhereSqlNode extends AbstractSqlNode { + + + public WhereSqlNode(Element element, ParameterMap parameterMap) { + super(element, parameterMap); + } + + @Override + public String getFullText() { + StringBuilder stringBuilder = new StringBuilder(); + if (element.isTextOnly()) { + return stringBuilder.toString(); + } + Iterator iterator = element.elementIterator(); + boolean firstMatch = true; + while(iterator.hasNext()) { + SqlNode sqlNode = SimpleSqlNodeFactory.getSqlNode((Element)iterator.next(), parameterMap); + if (!(sqlNode instanceof IfSqlNode)) { + throw new RuntimeException("Invalid xml tag inside where tag, must be 'if'"); + } + if (sqlNode.display()) { + String childText = sqlNode.getFullText(); + if (childText == null || childText.isEmpty()) { + continue; + } + childText = childText.trim(); + if (firstMatch) { + stringBuilder.append("WHERE "); + if (childText.toLowerCase().startsWith("and")) { + childText = childText.substring(3); + } + firstMatch = false; + } else { + if (!childText.toLowerCase().startsWith("and")) { + childText = "AND" + childText; + } + } + stringBuilder.append(childText).append(System.lineSeparator()); + } + } + return stringBuilder.toString(); + } + + @Override + public boolean display() { + return true; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/ExpressionUtils.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/ExpressionUtils.java new file mode 100644 index 0000000..91b9c67 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/ExpressionUtils.java @@ -0,0 +1,135 @@ +package com.xit.core.support.sql.parser.sqlNodes.expression; + +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import com.xit.core.support.sql.parser.parameter.exceptions.NoMarkValueFoundException; + +import java.util.regex.Pattern; + +public class ExpressionUtils { + + private static final String AND_KEYWORDS = " +AND +| +and +"; + + private static final String OR_KEYWORDS = " +OR +| +or +"; + + private ExpressionUtils() {} + + public static boolean isTrueExpression(String expression, ParameterMap parameterMap) { + checkIfExpressionIsValid(expression); + if (isAndExpression(expression)) { + return isTrueAndExpression(expression, parameterMap); + } + if (isOrExpression(expression)) { + return isTrueOrExpression(expression, parameterMap); + } + + return isTrueSingleExpression(expression, parameterMap); + } + + private static boolean isTrueAndExpression(String expression, ParameterMap parameterMap) { + String[]statements = expression.split(AND_KEYWORDS); + for (String singleStatement : statements) { + if (!isTrueSingleExpression(singleStatement, parameterMap)) { + return false; + } + } + return true; + } + + private static boolean isTrueOrExpression(String expression, ParameterMap parameterMap) { + String[]statements = expression.split(AND_KEYWORDS); + for (String singleStatement : statements) { + if (isTrueSingleExpression(singleStatement, parameterMap)) { + return true; + } + } + return false; + } + + /** + * Support statement author != null, author == null, author == 'yize', author != 'yize' + * + * @param expression + * @param parameterMap + * @return + */ + private static boolean isTrueSingleExpression(String expression, ParameterMap parameterMap) { + expression = expression.trim(); + for (Operator operator : Operator.values()) { + if (expression.contains(operator.toString())) { + String[]words = expression.split(operator.toString()); + if (words.length != 2) { + throw new InvalidExpressionException("Invalid expression " + expression); + } + String leftVal = words[0].trim(); + String rightVal = words[1].trim(); + String value = parameterMap.get(leftVal); + switch (operator) { + case EQUAL: { + if (rightVal.toLowerCase().equals("null")) { + return value == null; + } else { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return rightVal.equals(value); + } + } + case NOT_EQUAL: { + if (rightVal.toLowerCase().equals("null")) { + return value != null; + } else { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return !rightVal.equals(value); + } + } + case LARGE: { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return Integer.parseInt(value) > Integer.parseInt(rightVal); + } + case LARGE_EQUAL: { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return Integer.parseInt(value) >= Integer.parseInt(rightVal); + } + case LESS: { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return Integer.parseInt(value) < Integer.parseInt(rightVal); + } + case LESS_EQUAL: { + if (!parameterMap.containsKey(leftVal)) { + throw new NoMarkValueFoundException(leftVal); + } + return Integer.parseInt(value) <= Integer.parseInt(rightVal); + } + default: + return false; + } + } + } + return false; + } + + private static void checkIfExpressionIsValid(String expression) { + if (expression == null || expression.isEmpty()) { + throw new InvalidExpressionException("Expression is empty"); + } + if (isAndExpression(expression) && isOrExpression(expression)) { + throw new InvalidExpressionException("Expression doesn't support the combination of both 'AND' and 'OR' condition"); + } + } + + private static boolean isAndExpression(String expression) { + return Pattern.compile(AND_KEYWORDS).matcher(expression).find(); + } + + private static boolean isOrExpression(String expression) { + return Pattern.compile(OR_KEYWORDS).matcher(expression).find(); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/InvalidExpressionException.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/InvalidExpressionException.java new file mode 100644 index 0000000..9c1b009 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/InvalidExpressionException.java @@ -0,0 +1,8 @@ +package com.xit.core.support.sql.parser.sqlNodes.expression; + +public class InvalidExpressionException extends RuntimeException { + + public InvalidExpressionException(String errorMsg) { + super(errorMsg); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/Operator.java b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/Operator.java new file mode 100644 index 0000000..54907d4 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlNodes/expression/Operator.java @@ -0,0 +1,17 @@ +package com.xit.core.support.sql.parser.sqlNodes.expression; + +public enum Operator { + + EQUAL("=="), NOT_EQUAL("!="), LARGE_EQUAL(">="), LESS_EQUAL("<="), LESS("<"), LARGE(">"); + + private String operator; + + Operator(String operator) { + this.operator = operator; + } + + @Override + public String toString() { + return this.operator; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlQuery/BasicSqlQuery.java b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/BasicSqlQuery.java new file mode 100644 index 0000000..6f48c73 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/BasicSqlQuery.java @@ -0,0 +1,114 @@ +package com.xit.core.support.sql.parser.sqlQuery; +import com.xit.core.support.sql.parser.parameter.ParameterMap; +import com.xit.core.support.sql.parser.sqlNodes.SimpleSqlNodeFactory; +import com.xit.core.support.sql.parser.sqlNodes.SqlNode; +import org.dom4j.Element; + +import java.util.List; +import java.util.StringJoiner; + +public class BasicSqlQuery implements SqlQuery { + + private Element element; + + private ParameterMap parameterMap; + + public BasicSqlQuery(Element element) { + this.element = element; + this.parameterMap = new ParameterMap(); + } + + @Override + public SqlQuery setParameter(int pos, String value){ + this.parameterMap.put(pos, prevProcessValue(value)); + return this; + } + + @Override + public SqlQuery setParameter(int pos, int value) { + this.parameterMap.put(pos, String.valueOf(value)); + return this; + } + + @Override + public SqlQuery setParameter(int pos, boolean value) { + if (value) { + return setParameter(pos, 1); + } + return setParameter(pos, 0); + } + + @Override + public SqlQuery setParameter(int pos, List list) { + if (list == null) { + String nullStr = null; + return this.setParameter(pos, nullStr); + } + StringJoiner stringJoiner = new StringJoiner(",", "(", ")"); + for (String value : list) { + stringJoiner.add(prevProcessValue(value)); + } + parameterMap.put(pos, stringJoiner.toString()); + return this; + } + + @Override + public SqlQuery setParameter(String key, String value) { + this.parameterMap.put(key, prevProcessValue(value)); + return this; + } + + @Override + public SqlQuery setParameter(String key, boolean value) { + if (value) { + return setParameter(key, 1); + } + return setParameter(key, 0); + } + + @Override + public SqlQuery setParameter(String key, int value) { + this.parameterMap.put(key, String.valueOf(value)); + return this; + } + + @Override + public SqlQuery setParameter(String key, List list) { + if (list == null) { + String nullstr = null; + return this.setParameter(key, nullstr); + } + StringJoiner stringJoiner = new StringJoiner(",", "(", ")"); + for (String value : list) { + stringJoiner.add(prevProcessValue(value)); + } + parameterMap.put(key, stringJoiner.toString()); + return this; + } + + @Override + public String getQueryString() { + SqlNode rootSqlNode = SimpleSqlNodeFactory.getSqlNode(element, parameterMap); + return rootSqlNode.getFullText().trim() + .replaceAll(System.lineSeparator(), " ") + .replaceAll(" +", " "); + } + + private String prevProcessValue(String value) { + if (value == null) { + return null; + } + if (value.isEmpty()) { + return ""; + } + if (value.startsWith("'") && value.endsWith("'")) { + return value; + } + return "'" + value + "'"; + } + + @Override + public String toString() { + return getQueryString(); + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlQuery/EmptySqlQuery.java b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/EmptySqlQuery.java new file mode 100644 index 0000000..694e637 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/EmptySqlQuery.java @@ -0,0 +1,56 @@ +package com.xit.core.support.sql.parser.sqlQuery; + +import java.util.List; + +public class EmptySqlQuery implements SqlQuery { + + @Override + public SqlQuery setParameter(int pos, String value) { + return this; + } + + @Override + public SqlQuery setParameter(int pos, int value) { + return null; + } + + @Override + public SqlQuery setParameter(int pos, boolean value) { + return null; + } + + @Override + public SqlQuery setParameter(int pos, List list) { + return null; + } + + @Override + public SqlQuery setParameter(String key, String value) { + return this; + } + + @Override + public SqlQuery setParameter(String key, boolean value) { + return null; + } + + @Override + public SqlQuery setParameter(String key, int value) { + return null; + } + + @Override + public SqlQuery setParameter(String key, List list) { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String toString(){ + return "Empty Sql Query"; + } +} diff --git a/src/main/java/com/xit/core/support/sql/parser/sqlQuery/SqlQuery.java b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/SqlQuery.java new file mode 100644 index 0000000..6dc2ab5 --- /dev/null +++ b/src/main/java/com/xit/core/support/sql/parser/sqlQuery/SqlQuery.java @@ -0,0 +1,24 @@ +package com.xit.core.support.sql.parser.sqlQuery; + +import java.util.List; + +public interface SqlQuery { + + SqlQuery setParameter(int pos, String value); + + SqlQuery setParameter(int pos, int value); + + SqlQuery setParameter(int pos, boolean value); + + SqlQuery setParameter(int pos, List list); + + SqlQuery setParameter(String key, String value); + + SqlQuery setParameter(String key, boolean value); + + SqlQuery setParameter(String key, int value); + + SqlQuery setParameter(String key, List list); + + String getQueryString(); +} diff --git a/src/main/resources/sql/board-mapper.xml b/src/main/resources/sql/board-mapper.xml new file mode 100644 index 0000000..939a835 --- /dev/null +++ b/src/main/resources/sql/board-mapper.xml @@ -0,0 +1,31 @@ + + + + /* board-mapper|selectBoardList|julim */ + SELECT MCB.ci_code, + MU.name, + MCB.ci_contentno, + MCB.ci_title, + MCB.ci_contents, + MCB.ci_nalja, + MCB.ci_step, + MCB.ci_revel, + MCB.ci_ref, + MCB.ci_hit, + MCB.ci_pass, + MCB.ci_id + FROM min_civ_board680 MCB + LEFT OUTER JOIN min_userinfo MU + ON MCB.ci_id = MU.userid + WHERE 1=1 + AND INSTR(MCB.ci_title, #{ciTitle}) > 0 + AND MCB.ci_name like #{ciName}||'%' + AND INSTR(MCB.ci_contents, #{ciContents}) > 0 + ORDER BY MCB.ci_ref DESC, + MCB.ci_step ASC, + MCB.ci_code DESC + + + + + \ No newline at end of file diff --git a/src/main/resources/sql/board2-mapper.xml b/src/main/resources/sql/board2-mapper.xml deleted file mode 100644 index 8c7d6a3..0000000 --- a/src/main/resources/sql/board2-mapper.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - /* board-mapper|selectBoardList|julim */ - SELECT MCB.ci_code, - MU.name, - MCB.ci_contentno, - MCB.ci_title, - MCB.ci_contents, - MCB.ci_nalja, - MCB.ci_step, - MCB.ci_revel, - MCB.ci_ref, - MCB.ci_hit, - MCB.ci_pass, - MCB.ci_id - FROM min_civ_board680 MCB - LEFT OUTER JOIN min_userinfo MU - ON MCB.ci_id = MU.userid - WHERE 1=1 - AND INSTR(MCB.ci_title, #{ciTitle}) > 0 - AND MCB.ci_name like #{ciName}||'%' - ORDER BY MCB.ci_ref DESC, - MCB.ci_step ASC, - MCB.ci_code DESC - - - - - \ No newline at end of file diff --git a/src/main/resources/sql/refreshToken-mapper.xml b/src/main/resources/sql/refreshToken-mapper.xml index cb61909..7e4770e 100644 --- a/src/main/resources/sql/refreshToken-mapper.xml +++ b/src/main/resources/sql/refreshToken-mapper.xml @@ -1,30 +1,29 @@ - - + + /* refreshToken-mapper|selectRefreshToken|julim */ SELECT key, value FROM refresh_token - WHERE key = :userId - + WHERE key = #{key} + - + /* refreshToken-mapper|insertRefreshToken|julim */ INSERT INTO refresh_token ( key, value ) VALUE ( - :userId, - :tokenValue + #{key}, + #{value} ) - + - + /* refreshToken-mapper|updateRefreshToken|julim */ UPDATE refresh_token - SET value = :tokenValue - WHERE key = :userId - - - \ No newline at end of file + SET value = #{value} + WHERE key = #{key} + + \ No newline at end of file diff --git a/src/main/resources/sqlMapping.xml b/src/main/resources/sqlMapping.xml new file mode 100644 index 0000000..bd10827 --- /dev/null +++ b/src/main/resources/sqlMapping.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file