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