1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
package org.apache.ibatis.builder.xml; |
17 | |
|
18 | |
import java.io.InputStream; |
19 | |
import java.io.Reader; |
20 | |
import java.util.ArrayList; |
21 | |
import java.util.Collection; |
22 | |
import java.util.Collections; |
23 | |
import java.util.HashMap; |
24 | |
import java.util.Iterator; |
25 | |
import java.util.List; |
26 | |
import java.util.Map; |
27 | |
import java.util.Properties; |
28 | |
|
29 | |
import org.apache.ibatis.builder.BaseBuilder; |
30 | |
import org.apache.ibatis.builder.BuilderException; |
31 | |
import org.apache.ibatis.builder.CacheRefResolver; |
32 | |
import org.apache.ibatis.builder.IncompleteElementException; |
33 | |
import org.apache.ibatis.builder.MapperBuilderAssistant; |
34 | |
import org.apache.ibatis.builder.ResultMapResolver; |
35 | |
import org.apache.ibatis.cache.Cache; |
36 | |
import org.apache.ibatis.executor.ErrorContext; |
37 | |
import org.apache.ibatis.io.Resources; |
38 | |
import org.apache.ibatis.mapping.Discriminator; |
39 | |
import org.apache.ibatis.mapping.ParameterMapping; |
40 | |
import org.apache.ibatis.mapping.ParameterMode; |
41 | |
import org.apache.ibatis.mapping.ResultFlag; |
42 | |
import org.apache.ibatis.mapping.ResultMap; |
43 | |
import org.apache.ibatis.mapping.ResultMapping; |
44 | |
import org.apache.ibatis.parsing.XNode; |
45 | |
import org.apache.ibatis.parsing.XPathParser; |
46 | |
import org.apache.ibatis.session.Configuration; |
47 | |
import org.apache.ibatis.type.JdbcType; |
48 | |
import org.apache.ibatis.type.TypeHandler; |
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
public class XMLMapperBuilder extends BaseBuilder { |
54 | |
|
55 | |
private XPathParser parser; |
56 | |
private MapperBuilderAssistant builderAssistant; |
57 | |
private Map<String, XNode> sqlFragments; |
58 | |
private String resource; |
59 | |
|
60 | |
@Deprecated |
61 | |
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { |
62 | 0 | this(reader, configuration, resource, sqlFragments); |
63 | 0 | this.builderAssistant.setCurrentNamespace(namespace); |
64 | 0 | } |
65 | |
|
66 | |
@Deprecated |
67 | |
public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { |
68 | 0 | this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), |
69 | |
configuration, resource, sqlFragments); |
70 | 0 | } |
71 | |
|
72 | |
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) { |
73 | 85 | this(inputStream, configuration, resource, sqlFragments); |
74 | 85 | this.builderAssistant.setCurrentNamespace(namespace); |
75 | 85 | } |
76 | |
|
77 | |
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { |
78 | 399 | this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), |
79 | |
configuration, resource, sqlFragments); |
80 | 399 | } |
81 | |
|
82 | |
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { |
83 | 399 | super(configuration); |
84 | 399 | this.builderAssistant = new MapperBuilderAssistant(configuration, resource); |
85 | 399 | this.parser = parser; |
86 | 399 | this.sqlFragments = sqlFragments; |
87 | 399 | this.resource = resource; |
88 | 399 | } |
89 | |
|
90 | |
public void parse() { |
91 | 399 | if (!configuration.isResourceLoaded(resource)) { |
92 | 399 | configurationElement(parser.evalNode("/mapper")); |
93 | 393 | configuration.addLoadedResource(resource); |
94 | 393 | bindMapperForNamespace(); |
95 | |
} |
96 | |
|
97 | 393 | parsePendingResultMaps(); |
98 | 393 | parsePendingChacheRefs(); |
99 | 393 | parsePendingStatements(); |
100 | 393 | } |
101 | |
|
102 | |
public XNode getSqlFragment(String refid) { |
103 | 0 | return sqlFragments.get(refid); |
104 | |
} |
105 | |
|
106 | |
private void configurationElement(XNode context) { |
107 | |
try { |
108 | 399 | String namespace = context.getStringAttribute("namespace"); |
109 | 399 | if (namespace == null || namespace.equals("")) { |
110 | 2 | throw new BuilderException("Mapper's namespace cannot be empty"); |
111 | |
} |
112 | 397 | builderAssistant.setCurrentNamespace(namespace); |
113 | 396 | cacheRefElement(context.evalNode("cache-ref")); |
114 | 396 | cacheElement(context.evalNode("cache")); |
115 | 396 | parameterMapElement(context.evalNodes("/mapper/parameterMap")); |
116 | 396 | resultMapElements(context.evalNodes("/mapper/resultMap")); |
117 | 395 | sqlElement(context.evalNodes("/mapper/sql")); |
118 | 395 | buildStatementFromContext(context.evalNodes("select|insert|update|delete")); |
119 | 6 | } catch (Exception e) { |
120 | 6 | throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); |
121 | 393 | } |
122 | 393 | } |
123 | |
|
124 | |
private void buildStatementFromContext(List<XNode> list) { |
125 | 395 | if (configuration.getDatabaseId() != null) { |
126 | 2 | buildStatementFromContext(list, configuration.getDatabaseId()); |
127 | |
} |
128 | 395 | buildStatementFromContext(list, null); |
129 | 393 | } |
130 | |
|
131 | |
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { |
132 | 397 | for (XNode context : list) { |
133 | 1618 | final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); |
134 | |
try { |
135 | 1618 | statementParser.parseStatementNode(); |
136 | 34 | } catch (IncompleteElementException e) { |
137 | 34 | configuration.addIncompleteStatement(statementParser); |
138 | 1582 | } |
139 | 1616 | } |
140 | 395 | } |
141 | |
|
142 | |
private void parsePendingResultMaps() { |
143 | 393 | Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps(); |
144 | 393 | synchronized (incompleteResultMaps) { |
145 | 393 | Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator(); |
146 | 397 | while (iter.hasNext()) { |
147 | |
try { |
148 | 4 | iter.next().resolve(); |
149 | 2 | iter.remove(); |
150 | 2 | } catch (IncompleteElementException e) { |
151 | |
|
152 | 4 | } |
153 | |
} |
154 | 393 | } |
155 | 393 | } |
156 | |
|
157 | |
private void parsePendingChacheRefs() { |
158 | 393 | Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs(); |
159 | 393 | synchronized (incompleteCacheRefs) { |
160 | 393 | Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator(); |
161 | 417 | while (iter.hasNext()) { |
162 | |
try { |
163 | 24 | iter.next().resolveCacheRef(); |
164 | 12 | iter.remove(); |
165 | 12 | } catch (IncompleteElementException e) { |
166 | |
|
167 | 24 | } |
168 | |
} |
169 | 393 | } |
170 | 393 | } |
171 | |
|
172 | |
private void parsePendingStatements() { |
173 | 393 | Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements(); |
174 | 393 | synchronized (incompleteStatements) { |
175 | 393 | Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator(); |
176 | 458 | while (iter.hasNext()) { |
177 | |
try { |
178 | 65 | iter.next().parseStatementNode(); |
179 | 30 | iter.remove(); |
180 | 35 | } catch (IncompleteElementException e) { |
181 | |
|
182 | 65 | } |
183 | |
} |
184 | 393 | } |
185 | 393 | } |
186 | |
|
187 | |
private void cacheRefElement(XNode context) { |
188 | 396 | if (context != null) { |
189 | 12 | configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); |
190 | 12 | CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); |
191 | |
try { |
192 | 12 | cacheRefResolver.resolveCacheRef(); |
193 | 12 | } catch (IncompleteElementException e) { |
194 | 12 | configuration.addIncompleteCacheRef(cacheRefResolver); |
195 | 0 | } |
196 | |
} |
197 | 396 | } |
198 | |
|
199 | |
private void cacheElement(XNode context) throws Exception { |
200 | 396 | if (context != null) { |
201 | 43 | String type = context.getStringAttribute("type", "PERPETUAL"); |
202 | 43 | Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); |
203 | 43 | String eviction = context.getStringAttribute("eviction", "LRU"); |
204 | 43 | Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); |
205 | 43 | Long flushInterval = context.getLongAttribute("flushInterval"); |
206 | 43 | Integer size = context.getIntAttribute("size"); |
207 | 43 | boolean readWrite = !context.getBooleanAttribute("readOnly", false); |
208 | 43 | boolean blocking = context.getBooleanAttribute("blocking", false); |
209 | 43 | Properties props = context.getChildrenAsProperties(); |
210 | 43 | builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); |
211 | |
} |
212 | 396 | } |
213 | |
|
214 | |
private void parameterMapElement(List<XNode> list) throws Exception { |
215 | 396 | for (XNode parameterMapNode : list) { |
216 | 28 | String id = parameterMapNode.getStringAttribute("id"); |
217 | 28 | String type = parameterMapNode.getStringAttribute("type"); |
218 | 28 | Class<?> parameterClass = resolveClass(type); |
219 | 28 | List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter"); |
220 | 28 | List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); |
221 | 28 | for (XNode parameterNode : parameterNodes) { |
222 | 30 | String property = parameterNode.getStringAttribute("property"); |
223 | 30 | String javaType = parameterNode.getStringAttribute("javaType"); |
224 | 30 | String jdbcType = parameterNode.getStringAttribute("jdbcType"); |
225 | 30 | String resultMap = parameterNode.getStringAttribute("resultMap"); |
226 | 30 | String mode = parameterNode.getStringAttribute("mode"); |
227 | 30 | String typeHandler = parameterNode.getStringAttribute("typeHandler"); |
228 | 30 | Integer numericScale = parameterNode.getIntAttribute("numericScale"); |
229 | 30 | ParameterMode modeEnum = resolveParameterMode(mode); |
230 | 30 | Class<?> javaTypeClass = resolveClass(javaType); |
231 | 30 | JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); |
232 | |
@SuppressWarnings("unchecked") |
233 | 30 | Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); |
234 | 30 | ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale); |
235 | 30 | parameterMappings.add(parameterMapping); |
236 | 30 | } |
237 | 28 | builderAssistant.addParameterMap(id, parameterClass, parameterMappings); |
238 | 28 | } |
239 | 396 | } |
240 | |
|
241 | |
private void resultMapElements(List<XNode> list) throws Exception { |
242 | 396 | for (XNode resultMapNode : list) { |
243 | |
try { |
244 | 870 | resultMapElement(resultMapNode); |
245 | 2 | } catch (IncompleteElementException e) { |
246 | |
|
247 | 867 | } |
248 | 869 | } |
249 | 395 | } |
250 | |
|
251 | |
private ResultMap resultMapElement(XNode resultMapNode) throws Exception { |
252 | 870 | return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList()); |
253 | |
} |
254 | |
|
255 | |
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { |
256 | 1582 | ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); |
257 | 1582 | String id = resultMapNode.getStringAttribute("id", |
258 | |
resultMapNode.getValueBasedIdentifier()); |
259 | 1582 | String type = resultMapNode.getStringAttribute("type", |
260 | |
resultMapNode.getStringAttribute("ofType", |
261 | |
resultMapNode.getStringAttribute("resultType", |
262 | |
resultMapNode.getStringAttribute("javaType")))); |
263 | 1582 | String extend = resultMapNode.getStringAttribute("extends"); |
264 | 1582 | Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); |
265 | 1582 | Class<?> typeClass = resolveClass(type); |
266 | 1582 | Discriminator discriminator = null; |
267 | 1582 | List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); |
268 | 1582 | resultMappings.addAll(additionalResultMappings); |
269 | 1582 | List<XNode> resultChildren = resultMapNode.getChildren(); |
270 | 1582 | for (XNode resultChild : resultChildren) { |
271 | 2855 | if ("constructor".equals(resultChild.getName())) { |
272 | 197 | processConstructorElement(resultChild, typeClass, resultMappings); |
273 | 2658 | } else if ("discriminator".equals(resultChild.getName())) { |
274 | 109 | discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); |
275 | |
} else { |
276 | 2549 | List<ResultFlag> flags = new ArrayList<ResultFlag>(); |
277 | 2549 | if ("id".equals(resultChild.getName())) { |
278 | 709 | flags.add(ResultFlag.ID); |
279 | |
} |
280 | 2549 | resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); |
281 | |
} |
282 | 2854 | } |
283 | 1581 | ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); |
284 | |
try { |
285 | 1581 | return resultMapResolver.resolve(); |
286 | 2 | } catch (IncompleteElementException e) { |
287 | 2 | configuration.addIncompleteResultMap(resultMapResolver); |
288 | 2 | throw e; |
289 | |
} |
290 | |
} |
291 | |
|
292 | |
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception { |
293 | 197 | List<XNode> argChildren = resultChild.getChildren(); |
294 | 197 | for (XNode argChild : argChildren) { |
295 | 557 | List<ResultFlag> flags = new ArrayList<ResultFlag>(); |
296 | 557 | flags.add(ResultFlag.CONSTRUCTOR); |
297 | 557 | if ("idArg".equals(argChild.getName())) { |
298 | 199 | flags.add(ResultFlag.ID); |
299 | |
} |
300 | 557 | resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags)); |
301 | 557 | } |
302 | 197 | } |
303 | |
|
304 | |
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception { |
305 | 109 | String column = context.getStringAttribute("column"); |
306 | 109 | String javaType = context.getStringAttribute("javaType"); |
307 | 109 | String jdbcType = context.getStringAttribute("jdbcType"); |
308 | 109 | String typeHandler = context.getStringAttribute("typeHandler"); |
309 | 109 | Class<?> javaTypeClass = resolveClass(javaType); |
310 | |
@SuppressWarnings("unchecked") |
311 | 109 | Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); |
312 | 109 | JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); |
313 | 109 | Map<String, String> discriminatorMap = new HashMap<String, String>(); |
314 | 109 | for (XNode caseChild : context.getChildren()) { |
315 | 109 | String value = caseChild.getStringAttribute("value"); |
316 | 109 | String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings)); |
317 | 109 | discriminatorMap.put(value, resultMap); |
318 | 109 | } |
319 | 109 | return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap); |
320 | |
} |
321 | |
|
322 | |
private void sqlElement(List<XNode> list) throws Exception { |
323 | 395 | if (configuration.getDatabaseId() != null) { |
324 | 2 | sqlElement(list, configuration.getDatabaseId()); |
325 | |
} |
326 | 395 | sqlElement(list, null); |
327 | 395 | } |
328 | |
|
329 | |
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { |
330 | 397 | for (XNode context : list) { |
331 | 106 | String databaseId = context.getStringAttribute("databaseId"); |
332 | 106 | String id = context.getStringAttribute("id"); |
333 | 106 | id = builderAssistant.applyCurrentNamespace(id, false); |
334 | 106 | if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { |
335 | 100 | sqlFragments.put(id, context); |
336 | |
} |
337 | 106 | } |
338 | 397 | } |
339 | |
|
340 | |
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) { |
341 | 106 | if (requiredDatabaseId != null) { |
342 | 4 | if (!requiredDatabaseId.equals(databaseId)) { |
343 | 2 | return false; |
344 | |
} |
345 | |
} else { |
346 | 102 | if (databaseId != null) { |
347 | 2 | return false; |
348 | |
} |
349 | |
|
350 | 100 | if (this.sqlFragments.containsKey(id)) { |
351 | 2 | XNode context = this.sqlFragments.get(id); |
352 | 2 | if (context.getStringAttribute("databaseId") != null) { |
353 | 2 | return false; |
354 | |
} |
355 | |
} |
356 | |
} |
357 | 100 | return true; |
358 | |
} |
359 | |
|
360 | |
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { |
361 | 3106 | String property = context.getStringAttribute("property"); |
362 | 3106 | String column = context.getStringAttribute("column"); |
363 | 3106 | String javaType = context.getStringAttribute("javaType"); |
364 | 3106 | String jdbcType = context.getStringAttribute("jdbcType"); |
365 | 3106 | String nestedSelect = context.getStringAttribute("select"); |
366 | 3106 | String nestedResultMap = context.getStringAttribute("resultMap", |
367 | |
processNestedResultMappings(context, Collections.<ResultMapping> emptyList())); |
368 | 3106 | String notNullColumn = context.getStringAttribute("notNullColumn"); |
369 | 3106 | String columnPrefix = context.getStringAttribute("columnPrefix"); |
370 | 3106 | String typeHandler = context.getStringAttribute("typeHandler"); |
371 | 3106 | String resulSet = context.getStringAttribute("resultSet"); |
372 | 3106 | String foreignColumn = context.getStringAttribute("foreignColumn"); |
373 | 3106 | boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); |
374 | 3106 | Class<?> javaTypeClass = resolveClass(javaType); |
375 | |
@SuppressWarnings("unchecked") |
376 | 3106 | Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler); |
377 | 3106 | JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); |
378 | 3106 | return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy); |
379 | |
} |
380 | |
|
381 | |
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception { |
382 | 3215 | if ("association".equals(context.getName()) |
383 | |
|| "collection".equals(context.getName()) |
384 | |
|| "case".equals(context.getName())) { |
385 | 945 | if (context.getStringAttribute("select") == null) { |
386 | 712 | ResultMap resultMap = resultMapElement(context, resultMappings); |
387 | 712 | return resultMap.getId(); |
388 | |
} |
389 | |
} |
390 | 2503 | return null; |
391 | |
} |
392 | |
|
393 | |
private void bindMapperForNamespace() { |
394 | 393 | String namespace = builderAssistant.getCurrentNamespace(); |
395 | 393 | if (namespace != null) { |
396 | 393 | Class<?> boundType = null; |
397 | |
try { |
398 | 393 | boundType = Resources.classForName(namespace); |
399 | 149 | } catch (ClassNotFoundException e) { |
400 | |
|
401 | 244 | } |
402 | 393 | if (boundType != null) { |
403 | 244 | if (!configuration.hasMapper(boundType)) { |
404 | |
|
405 | |
|
406 | |
|
407 | 162 | configuration.addLoadedResource("namespace:" + namespace); |
408 | 162 | configuration.addMapper(boundType); |
409 | |
} |
410 | |
} |
411 | |
} |
412 | 393 | } |
413 | |
|
414 | |
} |