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