1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.ibatis.sqlmap.engine.builder.xml;
17
18 import com.ibatis.common.resources.Resources;
19 import com.ibatis.common.xml.NodeletException;
20 import com.ibatis.common.xml.NodeletParser;
21 import com.ibatis.common.xml.NodeletUtils;
22 import com.ibatis.sqlmap.client.SqlMapException;
23 import com.ibatis.sqlmap.engine.cache.CacheController;
24 import com.ibatis.sqlmap.engine.config.CacheModelConfig;
25 import com.ibatis.sqlmap.engine.config.ParameterMapConfig;
26 import com.ibatis.sqlmap.engine.config.ResultMapConfig;
27 import com.ibatis.sqlmap.engine.mapping.statement.DeleteStatement;
28 import com.ibatis.sqlmap.engine.mapping.statement.InsertStatement;
29 import com.ibatis.sqlmap.engine.mapping.statement.MappedStatement;
30 import com.ibatis.sqlmap.engine.mapping.statement.ProcedureStatement;
31 import com.ibatis.sqlmap.engine.mapping.statement.SelectStatement;
32 import com.ibatis.sqlmap.engine.mapping.statement.UpdateStatement;
33
34 import java.io.InputStream;
35 import java.io.Reader;
36 import java.util.Properties;
37
38
39
40
41 public class SqlMapParser {
42
43
44 private final NodeletParser parser;
45
46
47 private XmlParserState state;
48
49
50 private SqlStatementParser statementParser;
51
52
53
54
55
56
57
58 public SqlMapParser(XmlParserState state) {
59 this.parser = new NodeletParser();
60 this.state = state;
61
62 parser.setValidation(true);
63 parser.setEntityResolver(new SqlMapClasspathEntityResolver());
64
65 statementParser = new SqlStatementParser(this.state);
66
67 addSqlMapNodelets();
68 addSqlNodelets();
69 addTypeAliasNodelets();
70 addCacheModelNodelets();
71 addParameterMapNodelets();
72 addResultMapNodelets();
73 addStatementNodelets();
74
75 }
76
77
78
79
80
81
82
83
84
85
86 public void parse(Reader reader) throws NodeletException {
87 parser.parse(reader);
88 }
89
90
91
92
93
94
95
96
97
98
99 public void parse(InputStream inputStream) throws NodeletException {
100 parser.parse(inputStream);
101 }
102
103
104
105
106 private void addSqlMapNodelets() {
107 parser.addNodelet("/sqlMap", node -> {
108 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
109 state.setNamespace(attributes.getProperty("namespace"));
110 });
111 }
112
113
114
115
116 private void addSqlNodelets() {
117 parser.addNodelet("/sqlMap/sql", node -> {
118 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
119 String id = attributes.getProperty("id");
120 if (state.isUseStatementNamespaces()) {
121 id = state.applyNamespace(id);
122 }
123 if (state.getSqlIncludes().containsKey(id)) {
124 throw new SqlMapException("Duplicate <sql>-include '" + id + "' found.");
125 }
126 state.getSqlIncludes().put(id, node);
127 });
128 }
129
130
131
132
133 private void addTypeAliasNodelets() {
134 parser.addNodelet("/sqlMap/typeAlias", node -> {
135 Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
136 String alias = prop.getProperty("alias");
137 String type = prop.getProperty("type");
138 state.getConfig().getTypeHandlerFactory().putTypeAlias(alias, type);
139 });
140 }
141
142
143
144
145 private void addCacheModelNodelets() {
146 parser.addNodelet("/sqlMap/cacheModel", node -> {
147 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
148 String id = state.applyNamespace(attributes.getProperty("id"));
149 String type = attributes.getProperty("type");
150 String readOnlyAttr = attributes.getProperty("readOnly");
151 Boolean readOnly = readOnlyAttr == null || readOnlyAttr.isEmpty() ? null
152 : Boolean.valueOf("true".equals(readOnlyAttr));
153 String serializeAttr = attributes.getProperty("serialize");
154 Boolean serialize = serializeAttr == null || serializeAttr.isEmpty() ? null
155 : Boolean.valueOf("true".equals(serializeAttr));
156 type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
157 Class clazz = Resources.classForName(type);
158 if (readOnly == null) {
159 readOnly = Boolean.TRUE;
160 }
161 if (serialize == null) {
162 serialize = Boolean.FALSE;
163 }
164 CacheModelConfig cacheConfig = state.getConfig().newCacheModelConfig(id,
165 (CacheController) Resources.instantiate(clazz), readOnly.booleanValue(), serialize.booleanValue());
166 state.setCacheConfig(cacheConfig);
167 });
168 parser.addNodelet("/sqlMap/cacheModel/end()",
169 node -> state.getCacheConfig().setControllerProperties(state.getCacheProps()));
170 parser.addNodelet("/sqlMap/cacheModel/property", node -> {
171 state.getConfig().getErrorContext().setMoreInfo("Check the cache model properties.");
172 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
173 String name = attributes.getProperty("name");
174 String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
175 state.getCacheProps().setProperty(name, value);
176 });
177 parser.addNodelet("/sqlMap/cacheModel/flushOnExecute", node -> {
178 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
179 String statement = childAttributes.getProperty("statement");
180 state.getCacheConfig().addFlushTriggerStatement(statement);
181 });
182 parser.addNodelet("/sqlMap/cacheModel/flushInterval", node -> {
183 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
184 try {
185 int milliseconds = childAttributes.getProperty("milliseconds") == null ? 0
186 : Integer.parseInt(childAttributes.getProperty("milliseconds"));
187 int seconds = childAttributes.getProperty("seconds") == null ? 0
188 : Integer.parseInt(childAttributes.getProperty("seconds"));
189 int minutes = childAttributes.getProperty("minutes") == null ? 0
190 : Integer.parseInt(childAttributes.getProperty("minutes"));
191 int hours = childAttributes.getProperty("hours") == null ? 0
192 : Integer.parseInt(childAttributes.getProperty("hours"));
193 state.getCacheConfig().setFlushInterval(hours, minutes, seconds, milliseconds);
194 } catch (NumberFormatException e) {
195 throw new RuntimeException("Error building cache in '" + "resourceNAME"
196 + "'. Flush interval milliseconds must be a valid long integer value. Cause: " + e, e);
197 }
198 });
199 }
200
201
202
203
204 private void addParameterMapNodelets() {
205 parser.addNodelet("/sqlMap/parameterMap/end()", node -> {
206 state.getConfig().getErrorContext().setMoreInfo(null);
207 state.getConfig().getErrorContext().setObjectId(null);
208 state.setParamConfig(null);
209 });
210 parser.addNodelet("/sqlMap/parameterMap", node -> {
211 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
212 String id = state.applyNamespace(attributes.getProperty("id"));
213 String parameterClassName = attributes.getProperty("class");
214 parameterClassName = state.getConfig().getTypeHandlerFactory().resolveAlias(parameterClassName);
215 try {
216 state.getConfig().getErrorContext().setMoreInfo("Check the parameter class.");
217 ParameterMapConfig paramConf = state.getConfig().newParameterMapConfig(id,
218 Resources.classForName(parameterClassName));
219 state.setParamConfig(paramConf);
220 } catch (Exception e) {
221 throw new SqlMapException("Error configuring ParameterMap. Could not set ParameterClass. Cause: " + e, e);
222 }
223 });
224 parser.addNodelet("/sqlMap/parameterMap/parameter", node -> {
225 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
226 String propertyName = childAttributes.getProperty("property");
227 String jdbcType = childAttributes.getProperty("jdbcType");
228 String type = childAttributes.getProperty("typeName");
229 String javaType = childAttributes.getProperty("javaType");
230 String resultMap = state.applyNamespace(childAttributes.getProperty("resultMap"));
231 String nullValue = childAttributes.getProperty("nullValue");
232 String mode = childAttributes.getProperty("mode");
233 String callback = childAttributes.getProperty("typeHandler");
234 String numericScaleProp = childAttributes.getProperty("numericScale");
235
236 callback = state.getConfig().getTypeHandlerFactory().resolveAlias(callback);
237 Object typeHandlerImpl = null;
238 if (callback != null) {
239 typeHandlerImpl = Resources.instantiate(callback);
240 }
241
242 javaType = state.getConfig().getTypeHandlerFactory().resolveAlias(javaType);
243 Class javaClass = null;
244 try {
245 if (javaType != null && !javaType.isEmpty()) {
246 javaClass = Resources.classForName(javaType);
247 }
248 } catch (ClassNotFoundException e) {
249 throw new RuntimeException("Error setting javaType on parameter mapping. Cause: " + e);
250 }
251
252 Integer numericScale = null;
253 if (numericScaleProp != null) {
254 numericScale = Integer.valueOf(numericScaleProp);
255 }
256
257 state.getParamConfig().addParameterMapping(propertyName, javaClass, jdbcType, nullValue, mode, type, numericScale,
258 typeHandlerImpl, resultMap);
259 });
260 }
261
262
263
264
265 private void addResultMapNodelets() {
266 parser.addNodelet("/sqlMap/resultMap/end()", node -> {
267 state.getConfig().getErrorContext().setMoreInfo(null);
268 state.getConfig().getErrorContext().setObjectId(null);
269 });
270 parser.addNodelet("/sqlMap/resultMap", node -> {
271 Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
272 String id = state.applyNamespace(attributes.getProperty("id"));
273 String resultClassName = attributes.getProperty("class");
274 String extended = state.applyNamespace(attributes.getProperty("extends"));
275 String xmlName = attributes.getProperty("xmlName");
276 String groupBy = attributes.getProperty("groupBy");
277
278 resultClassName = state.getConfig().getTypeHandlerFactory().resolveAlias(resultClassName);
279 Class resultClass;
280 try {
281 state.getConfig().getErrorContext().setMoreInfo("Check the result class.");
282 resultClass = Resources.classForName(resultClassName);
283 } catch (Exception e) {
284 throw new RuntimeException("Error configuring Result. Could not set ResultClass. Cause: " + e, e);
285 }
286 ResultMapConfig resultConf = state.getConfig().newResultMapConfig(id, resultClass, groupBy, extended, xmlName);
287 state.setResultConfig(resultConf);
288 });
289 parser.addNodelet("/sqlMap/resultMap/result", node -> {
290 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
291 String propertyName = childAttributes.getProperty("property");
292 String nullValue = childAttributes.getProperty("nullValue");
293 String jdbcType = childAttributes.getProperty("jdbcType");
294 String javaType = childAttributes.getProperty("javaType");
295 String columnName = childAttributes.getProperty("column");
296 String columnIndexProp = childAttributes.getProperty("columnIndex");
297 String statementName = childAttributes.getProperty("select");
298 String resultMapName = childAttributes.getProperty("resultMap");
299 String callback = childAttributes.getProperty("typeHandler");
300 String notNullColumn = childAttributes.getProperty("notNullColumn");
301
302 state.getConfig().getErrorContext().setMoreInfo("Check the result mapping property type or name.");
303 Class javaClass = null;
304 try {
305 javaType = state.getConfig().getTypeHandlerFactory().resolveAlias(javaType);
306 if (javaType != null && !javaType.isEmpty()) {
307 javaClass = Resources.classForName(javaType);
308 }
309 } catch (ClassNotFoundException e) {
310 throw new RuntimeException("Error setting java type on result discriminator mapping. Cause: " + e);
311 }
312
313 state.getConfig().getErrorContext().setMoreInfo("Check the result mapping typeHandler attribute '" + callback
314 + "' (must be a TypeHandler or TypeHandlerCallback implementation).");
315 Object typeHandlerImpl = null;
316 try {
317 if (callback != null && !callback.isEmpty()) {
318 callback = state.getConfig().getTypeHandlerFactory().resolveAlias(callback);
319 typeHandlerImpl = Resources.instantiate(callback);
320 }
321 } catch (Exception e) {
322 throw new RuntimeException("Error occurred during custom type handler configuration. Cause: " + e, e);
323 }
324
325 Integer columnIndex = null;
326 if (columnIndexProp != null) {
327 try {
328 columnIndex = Integer.valueOf(columnIndexProp);
329 } catch (Exception e) {
330 throw new RuntimeException("Error parsing column index. Cause: " + e, e);
331 }
332 }
333
334 state.getResultConfig().addResultMapping(propertyName, columnName, columnIndex, javaClass, jdbcType, nullValue,
335 notNullColumn, statementName, resultMapName, typeHandlerImpl);
336 });
337
338 parser.addNodelet("/sqlMap/resultMap/discriminator/subMap", node -> {
339 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
340 String value = childAttributes.getProperty("value");
341 String resultMap = childAttributes.getProperty("resultMap");
342 resultMap = state.applyNamespace(resultMap);
343 state.getResultConfig().addDiscriminatorSubMap(value, resultMap);
344 });
345
346 parser.addNodelet("/sqlMap/resultMap/discriminator", node -> {
347 Properties childAttributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
348 String nullValue = childAttributes.getProperty("nullValue");
349 String jdbcType = childAttributes.getProperty("jdbcType");
350 String javaType = childAttributes.getProperty("javaType");
351 String columnName = childAttributes.getProperty("column");
352 String columnIndexProp = childAttributes.getProperty("columnIndex");
353 String callback = childAttributes.getProperty("typeHandler");
354
355 state.getConfig().getErrorContext().setMoreInfo("Check the disriminator type or name.");
356 Class javaClass = null;
357 try {
358 javaType = state.getConfig().getTypeHandlerFactory().resolveAlias(javaType);
359 if (javaType != null && !javaType.isEmpty()) {
360 javaClass = Resources.classForName(javaType);
361 }
362 } catch (ClassNotFoundException e) {
363 throw new RuntimeException("Error setting java type on result discriminator mapping. Cause: " + e);
364 }
365
366 state.getConfig().getErrorContext().setMoreInfo("Check the result mapping discriminator typeHandler attribute '"
367 + callback + "' (must be a TypeHandlerCallback implementation).");
368 Object typeHandlerImpl = null;
369 try {
370 if (callback != null && !callback.isEmpty()) {
371 callback = state.getConfig().getTypeHandlerFactory().resolveAlias(callback);
372 typeHandlerImpl = Resources.instantiate(callback);
373 }
374 } catch (Exception e) {
375 throw new RuntimeException("Error occurred during custom type handler configuration. Cause: " + e, e);
376 }
377
378 Integer columnIndex = null;
379 if (columnIndexProp != null) {
380 try {
381 columnIndex = Integer.valueOf(columnIndexProp);
382 } catch (Exception e) {
383 throw new RuntimeException("Error parsing column index. Cause: " + e, e);
384 }
385 }
386
387 state.getResultConfig().setDiscriminator(columnName, columnIndex, javaClass, jdbcType, nullValue,
388 typeHandlerImpl);
389 });
390 }
391
392
393
394
395 protected void addStatementNodelets() {
396 parser.addNodelet("/sqlMap/statement", node -> statementParser.parseGeneralStatement(node, new MappedStatement()));
397 parser.addNodelet("/sqlMap/insert", node -> statementParser.parseGeneralStatement(node, new InsertStatement()));
398 parser.addNodelet("/sqlMap/update", node -> statementParser.parseGeneralStatement(node, new UpdateStatement()));
399 parser.addNodelet("/sqlMap/delete", node -> statementParser.parseGeneralStatement(node, new DeleteStatement()));
400 parser.addNodelet("/sqlMap/select", node -> statementParser.parseGeneralStatement(node, new SelectStatement()));
401 parser.addNodelet("/sqlMap/procedure",
402 node -> statementParser.parseGeneralStatement(node, new ProcedureStatement()));
403 }
404
405 }