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.builder.xml;
17  
18  import java.util.List;
19  import java.util.Locale;
20  
21  import org.apache.ibatis.builder.BaseBuilder;
22  import org.apache.ibatis.builder.MapperBuilderAssistant;
23  import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
24  import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
25  import org.apache.ibatis.executor.keygen.KeyGenerator;
26  import org.apache.ibatis.executor.keygen.NoKeyGenerator;
27  import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
28  import org.apache.ibatis.mapping.MappedStatement;
29  import org.apache.ibatis.mapping.ResultSetType;
30  import org.apache.ibatis.mapping.SqlCommandType;
31  import org.apache.ibatis.mapping.SqlSource;
32  import org.apache.ibatis.mapping.StatementType;
33  import org.apache.ibatis.parsing.XNode;
34  import org.apache.ibatis.scripting.LanguageDriver;
35  import org.apache.ibatis.session.Configuration;
36  
37  /**
38   * @author Clinton Begin
39   */
40  public class XMLStatementBuilder extends BaseBuilder {
41  
42    private final MapperBuilderAssistant builderAssistant;
43    private final XNode context;
44    private final String requiredDatabaseId;
45  
46    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context) {
47      this(configuration, builderAssistant, context, null);
48    }
49  
50    public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context,
51        String databaseId) {
52      super(configuration);
53      this.builderAssistant = builderAssistant;
54      this.context = context;
55      this.requiredDatabaseId = databaseId;
56    }
57  
58    public void parseStatementNode() {
59      String id = context.getStringAttribute("id");
60      String databaseId = context.getStringAttribute("databaseId");
61  
62      if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
63        return;
64      }
65  
66      String nodeName = context.getNode().getNodeName();
67      SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
68      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
69      boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
70      boolean useCache = context.getBooleanAttribute("useCache", isSelect);
71      boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
72  
73      // Include Fragments before parsing
74      XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
75      includeParser.applyIncludes(context.getNode());
76  
77      String parameterType = context.getStringAttribute("parameterType");
78      Class<?> parameterTypeClass = resolveClass(parameterType);
79  
80      String lang = context.getStringAttribute("lang");
81      LanguageDriver langDriver = getLanguageDriver(lang);
82  
83      // Parse selectKey after includes and remove them.
84      processSelectKeyNodes(id, parameterTypeClass, langDriver);
85  
86      // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
87      KeyGenerator keyGenerator;
88      String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
89      keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
90      if (configuration.hasKeyGenerator(keyStatementId)) {
91        keyGenerator = configuration.getKeyGenerator(keyStatementId);
92      } else {
93        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
94            configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
95                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
96      }
97  
98      SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
99      StatementType statementType = StatementType
100         .valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
101     Integer fetchSize = context.getIntAttribute("fetchSize");
102     Integer timeout = context.getIntAttribute("timeout");
103     String parameterMap = context.getStringAttribute("parameterMap");
104     String resultType = context.getStringAttribute("resultType");
105     Class<?> resultTypeClass = resolveClass(resultType);
106     String resultMap = context.getStringAttribute("resultMap");
107     if (resultTypeClass == null && resultMap == null) {
108       resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id);
109     }
110     String resultSetType = context.getStringAttribute("resultSetType");
111     ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
112     if (resultSetTypeEnum == null) {
113       resultSetTypeEnum = configuration.getDefaultResultSetType();
114     }
115     String keyProperty = context.getStringAttribute("keyProperty");
116     String keyColumn = context.getStringAttribute("keyColumn");
117     String resultSets = context.getStringAttribute("resultSets");
118     boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);
119 
120     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
121         parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
122         keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
123   }
124 
125   private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
126     List<XNode> selectKeyNodes = context.evalNodes("selectKey");
127     if (configuration.getDatabaseId() != null) {
128       parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
129     }
130     parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
131     removeSelectKeyNodes(selectKeyNodes);
132   }
133 
134   private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass,
135       LanguageDriver langDriver, String skRequiredDatabaseId) {
136     for (XNode nodeToHandle : list) {
137       String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
138       String databaseId = nodeToHandle.getStringAttribute("databaseId");
139       if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
140         parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
141       }
142     }
143   }
144 
145   private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver,
146       String databaseId) {
147     String resultType = nodeToHandle.getStringAttribute("resultType");
148     Class<?> resultTypeClass = resolveClass(resultType);
149     StatementType statementType = StatementType
150         .valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
151     String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
152     String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
153     boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));
154 
155     // defaults
156     boolean useCache = false;
157     boolean resultOrdered = false;
158     KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
159     Integer fetchSize = null;
160     Integer timeout = null;
161     boolean flushCache = false;
162     String parameterMap = null;
163     String resultMap = null;
164     ResultSetType resultSetTypeEnum = null;
165 
166     SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
167     SqlCommandType sqlCommandType = SqlCommandType.SELECT;
168 
169     builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
170         parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
171         keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null, false);
172 
173     id = builderAssistant.applyCurrentNamespace(id, false);
174 
175     MappedStatement keyStatement = configuration.getMappedStatement(id, false);
176     configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
177   }
178 
179   private void removeSelectKeyNodes(List<XNode> selectKeyNodes) {
180     for (XNode nodeToHandle : selectKeyNodes) {
181       nodeToHandle.getParent().getNode().removeChild(nodeToHandle.getNode());
182     }
183   }
184 
185   private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
186     if (requiredDatabaseId != null) {
187       return requiredDatabaseId.equals(databaseId);
188     }
189     if (databaseId != null) {
190       return false;
191     }
192     id = builderAssistant.applyCurrentNamespace(id, false);
193     if (!this.configuration.hasStatement(id, false)) {
194       return true;
195     }
196     // skip this statement if there is a previous one with a not null databaseId
197     MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
198     return previous.getDatabaseId() == null;
199   }
200 
201   private LanguageDriver getLanguageDriver(String lang) {
202     Class<? extends LanguageDriver> langClass = null;
203     if (lang != null) {
204       langClass = resolveClass(lang);
205     }
206     return configuration.getLanguageDriver(langClass);
207   }
208 
209 }