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