diff --git a/pom.xml b/pom.xml index 1c8135f..87aaddb 100755 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,11 @@ commons-lang3 3.14.0 + + ognl + ognl + 3.4.2 + diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java index 15822b8..2e4eaf5 100644 --- a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java @@ -8,10 +8,15 @@ public class DynamicContext { private final StringJoiner sqlBuilder = new StringJoiner(" "); + private Object parameterObject; public DynamicContext() { } + public DynamicContext(Object parameterObject) { + this.parameterObject = parameterObject; + } + public void appendSql(String sql) { sqlBuilder.add(sql); } @@ -19,4 +24,12 @@ public void appendSql(String sql) { public String getSql() { return sqlBuilder.toString().trim(); } + + public Object getParameterObject() { + return parameterObject; + } + + public void setParameterObject(Object parameterObject) { + this.parameterObject = parameterObject; + } } diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java new file mode 100644 index 0000000..dd3009b --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicSqlSource.java @@ -0,0 +1,30 @@ +package org.apache.ibatis.scripting.xmltags; + +import org.apache.ibatis.builder.SqlSourceBuilder; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.session.Configuration; + +/** + * @author furious 2024/4/16 + */ +public class DynamicSqlSource implements SqlSource { + + private final Configuration configuration; + private final SqlNode rootSqlNode; + + public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { + this.configuration = configuration; + this.rootSqlNode = rootSqlNode; + } + + @Override + public BoundSql getBoundSql(Object parameterObject) { + DynamicContext context = new DynamicContext(parameterObject); + rootSqlNode.apply(context); + SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); + Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); + SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType); + return sqlSource.getBoundSql(parameterObject); + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java new file mode 100644 index 0000000..80a2415 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/IfSqlNode.java @@ -0,0 +1,30 @@ +package org.apache.ibatis.scripting.xmltags; + +import ognl.Ognl; + +/** + * @author furious 2024/4/16 + */ +public class IfSqlNode implements SqlNode { + + private final String test; + private final SqlNode contents; + + public IfSqlNode(String test, SqlNode contents) { + this.test = test; + this.contents = contents; + } + + @Override + public boolean apply(DynamicContext context) { + try { + Object value = Ognl.getValue(Ognl.parseExpression(test), context.getParameterObject()); + if (value instanceof Boolean && (Boolean) value) { + contents.apply(context); + } + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java new file mode 100644 index 0000000..1d057a4 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/TrimSqlNode.java @@ -0,0 +1,89 @@ +package org.apache.ibatis.scripting.xmltags; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +/** + * @author furious 2024/4/16 + */ +public class TrimSqlNode implements SqlNode { + + private final SqlNode contents; + private final String prefix; + private final List prefixesToOverride; + + public TrimSqlNode(SqlNode contents, String prefix, String prefixesToOverride) { + this.contents = contents; + this.prefix = prefix; + this.prefixesToOverride = parseOverrides(prefixesToOverride); + } + + @Override + public boolean apply(DynamicContext context) { + FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context); + boolean result = contents.apply(filteredDynamicContext); + filteredDynamicContext.applyAll(); + return result; + } + + private static List parseOverrides(String overrides) { + if (StringUtils.isEmpty(overrides)) { + return Collections.emptyList(); + } + return Arrays.stream(overrides.split("\\|")).collect(Collectors.toList()); + } + + private class FilteredDynamicContext extends DynamicContext { + private final DynamicContext delegate; + private StringBuilder sqlBuffer = new StringBuilder(); + + public FilteredDynamicContext(DynamicContext delegate) { + this.delegate = delegate; + } + + public void applyAll() { + sqlBuffer = new StringBuilder(sqlBuffer.toString().trim()); + String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(); + if (trimmedUppercaseSql.length() > 0) { + applyPrefix(sqlBuffer, trimmedUppercaseSql); + } + delegate.appendSql(sqlBuffer.toString()); + } + + @Override + public void appendSql(String sql) { + sqlBuffer.append(sql); + } + + @Override + public String getSql() { + return delegate.getSql(); + } + + @Override + public Object getParameterObject() { + return delegate.getParameterObject(); + } + + private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { + if (Objects.nonNull(prefixesToOverride)) { + for (String remove : prefixesToOverride) { + if (trimmedUppercaseSql.startsWith(remove)) { + sql.delete(0, remove.trim().length()); + break; + } + } + } + if (prefix != null) { + sql.insert(0, " "); + sql.insert(0, prefix); + } + } + } + +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java index b0a82c5..5646254 100644 --- a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java @@ -1,9 +1,12 @@ package org.apache.ibatis.scripting.xmltags; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.ibatis.builder.BaseBuilder; +import org.apache.ibatis.builder.BuilderException; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.parsing.XNode; import org.apache.ibatis.scripting.defaults.RawSqlSource; @@ -18,6 +21,8 @@ public class XMLScriptBuilder extends BaseBuilder { private final XNode context; private final Class parameterType; + private boolean isDynamic; + private final Map nodeHandlerMap = new HashMap<>(); public XMLScriptBuilder(Configuration configuration, XNode context) { this(configuration, context, null); @@ -27,10 +32,15 @@ public XMLScriptBuilder(Configuration configuration, XNode context, Class par super(configuration); this.context = context; this.parameterType = parameterType; + nodeHandlerMap.put("if", new IfHandler()); + nodeHandlerMap.put("trim", new TrimHandler()); } public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = parseDynamicTags(context); + if (isDynamic) { + return new DynamicSqlSource(configuration, rootSqlNode); + } return new RawSqlSource(configuration, rootSqlNode, parameterType); } @@ -41,8 +51,49 @@ protected MixedSqlNode parseDynamicTags(XNode node) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.TEXT_NODE) { contents.add(new StaticTextSqlNode(child.getBody())); + } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { + String nodeName = child.getNode().getNodeName(); + NodeHandler handler = nodeHandlerMap.get(nodeName); + if (handler == null) { + throw new BuilderException("not support " + nodeName + " NodeHandler"); + } + handler.handleNode(child, contents); + isDynamic = true; } } return new MixedSqlNode(contents); } + + private interface NodeHandler { + void handleNode(XNode nodeToHandle, List targetContents); + } + + /** + * 处理 标签 + */ + private class IfHandler implements NodeHandler { + + @Override + public void handleNode(XNode nodeToHandle, List targetContents) { + MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); + String test = nodeToHandle.getAttribute("test"); + IfSqlNode ifSqlNode = new IfSqlNode(test, mixedSqlNode); + targetContents.add(ifSqlNode); + } + } + + /** + * 处理 标签 + */ + private class TrimHandler implements NodeHandler { + + @Override + public void handleNode(XNode nodeToHandle, List targetContents) { + MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); + String prefix = nodeToHandle.getAttribute("prefix"); + String prefixOverrides = nodeToHandle.getAttribute("prefixOverrides"); + TrimSqlNode trim = new TrimSqlNode(mixedSqlNode, prefix, prefixOverrides); + targetContents.add(trim); + } + } }