View Javadoc
1   /*
2    *    Copyright 2009-2023 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  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.ibatis.builder.BaseBuilder;
24  import org.apache.ibatis.builder.BuilderException;
25  import org.apache.ibatis.mapping.SqlSource;
26  import org.apache.ibatis.parsing.XNode;
27  import org.apache.ibatis.scripting.defaults.RawSqlSource;
28  import org.apache.ibatis.session.Configuration;
29  import org.w3c.dom.Node;
30  import org.w3c.dom.NodeList;
31  
32  /**
33   * @author Clinton Begin
34   */
35  public class XMLScriptBuilder extends BaseBuilder {
36  
37    private final XNode context;
38    private boolean isDynamic;
39    private final Class<?> parameterType;
40    private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
41  
42    public XMLScriptBuilder(Configuration configuration, XNode context) {
43      this(configuration, context, null);
44    }
45  
46    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
47      super(configuration);
48      this.context = context;
49      this.parameterType = parameterType;
50      initNodeHandlerMap();
51    }
52  
53    private void initNodeHandlerMap() {
54      nodeHandlerMap.put("trim", new TrimHandler());
55      nodeHandlerMap.put("where", new WhereHandler());
56      nodeHandlerMap.put("set", new SetHandler());
57      nodeHandlerMap.put("foreach", new ForEachHandler());
58      nodeHandlerMap.put("if", new IfHandler());
59      nodeHandlerMap.put("choose", new ChooseHandler());
60      nodeHandlerMap.put("when", new IfHandler());
61      nodeHandlerMap.put("otherwise", new OtherwiseHandler());
62      nodeHandlerMap.put("bind", new BindHandler());
63    }
64  
65    public SqlSource parseScriptNode() {
66      MixedSqlNode rootSqlNode = parseDynamicTags(context);
67      SqlSource sqlSource;
68      if (isDynamic) {
69        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
70      } else {
71        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
72      }
73      return sqlSource;
74    }
75  
76    protected MixedSqlNode parseDynamicTags(XNode node) {
77      List<SqlNode> contents = new ArrayList<>();
78      NodeList children = node.getNode().getChildNodes();
79      for (int i = 0; i < children.getLength(); i++) {
80        XNode child = node.newXNode(children.item(i));
81        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
82          String data = child.getStringBody("");
83          TextSqlNode textSqlNode = new TextSqlNode(data);
84          if (textSqlNode.isDynamic()) {
85            contents.add(textSqlNode);
86            isDynamic = true;
87          } else {
88            contents.add(new StaticTextSqlNode(data));
89          }
90        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
91          String nodeName = child.getNode().getNodeName();
92          NodeHandler handler = nodeHandlerMap.get(nodeName);
93          if (handler == null) {
94            throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
95          }
96          handler.handleNode(child, contents);
97          isDynamic = true;
98        }
99      }
100     return new MixedSqlNode(contents);
101   }
102 
103   private interface NodeHandler {
104     void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
105   }
106 
107   private static class BindHandler implements NodeHandler {
108     public BindHandler() {
109       // Prevent Synthetic Access
110     }
111 
112     @Override
113     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
114       final String name = nodeToHandle.getStringAttribute("name");
115       final String expression = nodeToHandle.getStringAttribute("value");
116       final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
117       targetContents.add(node);
118     }
119   }
120 
121   private class TrimHandler implements NodeHandler {
122     public TrimHandler() {
123       // Prevent Synthetic Access
124     }
125 
126     @Override
127     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
128       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
129       String prefix = nodeToHandle.getStringAttribute("prefix");
130       String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
131       String suffix = nodeToHandle.getStringAttribute("suffix");
132       String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
133       TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
134       targetContents.add(trim);
135     }
136   }
137 
138   private class WhereHandler implements NodeHandler {
139     public WhereHandler() {
140       // Prevent Synthetic Access
141     }
142 
143     @Override
144     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
145       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
146       WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
147       targetContents.add(where);
148     }
149   }
150 
151   private class SetHandler implements NodeHandler {
152     public SetHandler() {
153       // Prevent Synthetic Access
154     }
155 
156     @Override
157     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
158       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
159       SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
160       targetContents.add(set);
161     }
162   }
163 
164   private class ForEachHandler implements NodeHandler {
165     public ForEachHandler() {
166       // Prevent Synthetic Access
167     }
168 
169     @Override
170     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
171       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
172       String collection = nodeToHandle.getStringAttribute("collection");
173       Boolean nullable = nodeToHandle.getBooleanAttribute("nullable");
174       String item = nodeToHandle.getStringAttribute("item");
175       String index = nodeToHandle.getStringAttribute("index");
176       String open = nodeToHandle.getStringAttribute("open");
177       String close = nodeToHandle.getStringAttribute("close");
178       String separator = nodeToHandle.getStringAttribute("separator");
179       ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, nullable, index, item,
180           open, close, separator);
181       targetContents.add(forEachSqlNode);
182     }
183   }
184 
185   private class IfHandler implements NodeHandler {
186     public IfHandler() {
187       // Prevent Synthetic Access
188     }
189 
190     @Override
191     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
192       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
193       String test = nodeToHandle.getStringAttribute("test");
194       IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
195       targetContents.add(ifSqlNode);
196     }
197   }
198 
199   private class OtherwiseHandler implements NodeHandler {
200     public OtherwiseHandler() {
201       // Prevent Synthetic Access
202     }
203 
204     @Override
205     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
206       MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
207       targetContents.add(mixedSqlNode);
208     }
209   }
210 
211   private class ChooseHandler implements NodeHandler {
212     public ChooseHandler() {
213       // Prevent Synthetic Access
214     }
215 
216     @Override
217     public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
218       List<SqlNode> whenSqlNodes = new ArrayList<>();
219       List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
220       handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
221       SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
222       ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
223       targetContents.add(chooseSqlNode);
224     }
225 
226     private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes,
227         List<SqlNode> defaultSqlNodes) {
228       List<XNode> children = chooseSqlNode.getChildren();
229       for (XNode child : children) {
230         String nodeName = child.getNode().getNodeName();
231         NodeHandler handler = nodeHandlerMap.get(nodeName);
232         if (handler instanceof IfHandler) {
233           handler.handleNode(child, ifSqlNodes);
234         } else if (handler instanceof OtherwiseHandler) {
235           handler.handleNode(child, defaultSqlNodes);
236         }
237       }
238     }
239 
240     private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
241       SqlNode defaultSqlNode = null;
242       if (defaultSqlNodes.size() == 1) {
243         defaultSqlNode = defaultSqlNodes.get(0);
244       } else if (defaultSqlNodes.size() > 1) {
245         throw new BuilderException("Too many default (otherwise) elements in choose statement.");
246       }
247       return defaultSqlNode;
248     }
249   }
250 
251 }