XMLScriptBuilder.java

  1. /*
  2.  *    Copyright 2009-2025 the original author or authors.
  3.  *
  4.  *    Licensed under the Apache License, Version 2.0 (the "License");
  5.  *    you may not use this file except in compliance with the License.
  6.  *    You may obtain a copy of the License at
  7.  *
  8.  *       https://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  *    Unless required by applicable law or agreed to in writing, software
  11.  *    distributed under the License is distributed on an "AS IS" BASIS,
  12.  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  *    See the License for the specific language governing permissions and
  14.  *    limitations under the License.
  15.  */
  16. package org.apache.ibatis.scripting.xmltags;

  17. import java.util.ArrayList;
  18. import java.util.HashMap;
  19. import java.util.List;
  20. import java.util.Map;

  21. import org.apache.ibatis.builder.BaseBuilder;
  22. import org.apache.ibatis.builder.BuilderException;
  23. import org.apache.ibatis.mapping.SqlSource;
  24. import org.apache.ibatis.parsing.XNode;
  25. import org.apache.ibatis.scripting.defaults.RawSqlSource;
  26. import org.apache.ibatis.session.Configuration;
  27. import org.w3c.dom.Node;
  28. import org.w3c.dom.NodeList;

  29. /**
  30.  * @author Clinton Begin
  31.  */
  32. public class XMLScriptBuilder extends BaseBuilder {

  33.   private final XNode context;
  34.   private boolean isDynamic;
  35.   private final Class<?> parameterType;
  36.   private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();

  37.   public XMLScriptBuilder(Configuration configuration, XNode context) {
  38.     this(configuration, context, null);
  39.   }

  40.   public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
  41.     super(configuration);
  42.     this.context = context;
  43.     this.parameterType = parameterType;
  44.     initNodeHandlerMap();
  45.   }

  46.   private void initNodeHandlerMap() {
  47.     nodeHandlerMap.put("trim", new TrimHandler());
  48.     nodeHandlerMap.put("where", new WhereHandler());
  49.     nodeHandlerMap.put("set", new SetHandler());
  50.     nodeHandlerMap.put("foreach", new ForEachHandler());
  51.     nodeHandlerMap.put("if", new IfHandler());
  52.     nodeHandlerMap.put("choose", new ChooseHandler());
  53.     nodeHandlerMap.put("when", new IfHandler());
  54.     nodeHandlerMap.put("otherwise", new OtherwiseHandler());
  55.     nodeHandlerMap.put("bind", new BindHandler());
  56.   }

  57.   public SqlSource parseScriptNode() {
  58.     MixedSqlNode rootSqlNode = parseDynamicTags(context);
  59.     SqlSource sqlSource;
  60.     if (isDynamic) {
  61.       sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  62.     } else {
  63.       sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  64.     }
  65.     return sqlSource;
  66.   }

  67.   protected MixedSqlNode parseDynamicTags(XNode node) {
  68.     List<SqlNode> contents = new ArrayList<>();
  69.     NodeList children = node.getNode().getChildNodes();
  70.     for (int i = 0; i < children.getLength(); i++) {
  71.       XNode child = node.newXNode(children.item(i));
  72.       if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
  73.         String data = child.getStringBody("");
  74.         TextSqlNode textSqlNode = new TextSqlNode(data);
  75.         if (textSqlNode.isDynamic()) {
  76.           contents.add(textSqlNode);
  77.           isDynamic = true;
  78.         } else {
  79.           contents.add(new StaticTextSqlNode(data));
  80.         }
  81.       } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
  82.         String nodeName = child.getNode().getNodeName();
  83.         NodeHandler handler = nodeHandlerMap.get(nodeName);
  84.         if (handler == null) {
  85.           throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
  86.         }
  87.         handler.handleNode(child, contents);
  88.         isDynamic = true;
  89.       }
  90.     }
  91.     return new MixedSqlNode(contents);
  92.   }

  93.   private interface NodeHandler {
  94.     void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
  95.   }

  96.   private static class BindHandler implements NodeHandler {
  97.     public BindHandler() {
  98.       // Prevent Synthetic Access
  99.     }

  100.     @Override
  101.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  102.       final String name = nodeToHandle.getStringAttribute("name");
  103.       final String expression = nodeToHandle.getStringAttribute("value");
  104.       final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
  105.       targetContents.add(node);
  106.     }
  107.   }

  108.   private class TrimHandler implements NodeHandler {
  109.     public TrimHandler() {
  110.       // Prevent Synthetic Access
  111.     }

  112.     @Override
  113.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  114.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  115.       String prefix = nodeToHandle.getStringAttribute("prefix");
  116.       String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
  117.       String suffix = nodeToHandle.getStringAttribute("suffix");
  118.       String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
  119.       TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
  120.       targetContents.add(trim);
  121.     }
  122.   }

  123.   private class WhereHandler implements NodeHandler {
  124.     public WhereHandler() {
  125.       // Prevent Synthetic Access
  126.     }

  127.     @Override
  128.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  129.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  130.       WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
  131.       targetContents.add(where);
  132.     }
  133.   }

  134.   private class SetHandler implements NodeHandler {
  135.     public SetHandler() {
  136.       // Prevent Synthetic Access
  137.     }

  138.     @Override
  139.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  140.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  141.       SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
  142.       targetContents.add(set);
  143.     }
  144.   }

  145.   private class ForEachHandler implements NodeHandler {
  146.     public ForEachHandler() {
  147.       // Prevent Synthetic Access
  148.     }

  149.     @Override
  150.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  151.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  152.       String collection = nodeToHandle.getStringAttribute("collection");
  153.       Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
  154.       String item = nodeToHandle.getStringAttribute("item");
  155.       String index = nodeToHandle.getStringAttribute("index");
  156.       String open = nodeToHandle.getStringAttribute("open");
  157.       String close = nodeToHandle.getStringAttribute("close");
  158.       String separator = nodeToHandle.getStringAttribute("separator");
  159.       ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item,
  160.           open, close, separator);
  161.       targetContents.add(forEachSqlNode);
  162.     }
  163.   }

  164.   private class IfHandler implements NodeHandler {
  165.     public IfHandler() {
  166.       // Prevent Synthetic Access
  167.     }

  168.     @Override
  169.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  170.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  171.       String test = nodeToHandle.getStringAttribute("test");
  172.       IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
  173.       targetContents.add(ifSqlNode);
  174.     }
  175.   }

  176.   private class OtherwiseHandler implements NodeHandler {
  177.     public OtherwiseHandler() {
  178.       // Prevent Synthetic Access
  179.     }

  180.     @Override
  181.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  182.       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
  183.       targetContents.add(mixedSqlNode);
  184.     }
  185.   }

  186.   private class ChooseHandler implements NodeHandler {
  187.     public ChooseHandler() {
  188.       // Prevent Synthetic Access
  189.     }

  190.     @Override
  191.     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
  192.       List<SqlNode> whenSqlNodes = new ArrayList<>();
  193.       List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
  194.       handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
  195.       SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
  196.       ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
  197.       targetContents.add(chooseSqlNode);
  198.     }

  199.     private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes,
  200.         List<SqlNode> defaultSqlNodes) {
  201.       List<XNode> children = chooseSqlNode.getChildren();
  202.       for (XNode child : children) {
  203.         String nodeName = child.getNode().getNodeName();
  204.         NodeHandler handler = nodeHandlerMap.get(nodeName);
  205.         if (handler instanceof IfHandler) {
  206.           handler.handleNode(child, ifSqlNodes);
  207.         } else if (handler instanceof OtherwiseHandler) {
  208.           handler.handleNode(child, defaultSqlNodes);
  209.         }
  210.       }
  211.     }

  212.     private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
  213.       SqlNode defaultSqlNode = null;
  214.       if (defaultSqlNodes.size() == 1) {
  215.         defaultSqlNode = defaultSqlNodes.get(0);
  216.       } else if (defaultSqlNodes.size() > 1) {
  217.         throw new BuilderException("Too many default (otherwise) elements in choose statement.");
  218.       }
  219.       return defaultSqlNode;
  220.     }
  221.   }

  222. }