View Javadoc
1   /*
2    * Copyright 2004-2026 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.NodeletParser;
20  import com.ibatis.common.xml.NodeletUtils;
21  import com.ibatis.sqlmap.client.SqlMapClient;
22  import com.ibatis.sqlmap.client.SqlMapException;
23  import com.ibatis.sqlmap.engine.config.SqlMapConfiguration;
24  import com.ibatis.sqlmap.engine.datasource.DataSourceFactory;
25  import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactory;
26  import com.ibatis.sqlmap.engine.transaction.TransactionConfig;
27  import com.ibatis.sqlmap.engine.transaction.TransactionManager;
28  
29  import java.io.InputStream;
30  import java.io.Reader;
31  import java.util.Properties;
32  
33  /**
34   * The Class SqlMapConfigParser.
35   */
36  public class SqlMapConfigParser {
37  
38    /** The parser. */
39    protected final NodeletParser parser = new NodeletParser();
40  
41    /** The state. */
42    private XmlParserState state = new XmlParserState();
43  
44    /** The using streams. */
45    private boolean usingStreams = false;
46  
47    /**
48     * Instantiates a new sql map config parser.
49     */
50    public SqlMapConfigParser() {
51      parser.setValidation(true);
52      parser.setEntityResolver(new SqlMapClasspathEntityResolver());
53  
54      addSqlMapConfigNodelets();
55      addGlobalPropNodelets();
56      addSettingsNodelets();
57      addTypeAliasNodelets();
58      addTypeHandlerNodelets();
59      addTransactionManagerNodelets();
60      addSqlMapNodelets();
61      addResultObjectFactoryNodelets();
62  
63    }
64  
65    /**
66     * Parses the.
67     *
68     * @param reader
69     *          the reader
70     * @param props
71     *          the props
72     *
73     * @return the sql map client
74     */
75    public SqlMapClient parse(Reader reader, Properties props) {
76      if (props != null) {
77        state.setGlobalProps(props);
78      }
79      return parse(reader);
80    }
81  
82    /**
83     * Parses the.
84     *
85     * @param reader
86     *          the reader
87     *
88     * @return the sql map client
89     */
90    public SqlMapClient parse(Reader reader) {
91      try {
92        usingStreams = false;
93  
94        parser.parse(reader);
95        return state.getConfig().getClient();
96      } catch (Exception e) {
97        throw new RuntimeException("Error occurred.  Cause: " + e, e);
98      }
99    }
100 
101   /**
102    * Parses the.
103    *
104    * @param inputStream
105    *          the input stream
106    * @param props
107    *          the props
108    *
109    * @return the sql map client
110    */
111   public SqlMapClient parse(InputStream inputStream, Properties props) {
112     if (props != null) {
113       state.setGlobalProps(props);
114     }
115     return parse(inputStream);
116   }
117 
118   /**
119    * Parses the.
120    *
121    * @param inputStream
122    *          the input stream
123    *
124    * @return the sql map client
125    */
126   public SqlMapClient parse(InputStream inputStream) {
127     try {
128       usingStreams = true;
129 
130       parser.parse(inputStream);
131       return state.getConfig().getClient();
132     } catch (Exception e) {
133       throw new RuntimeException("Error occurred.  Cause: " + e, e);
134     }
135   }
136 
137   /**
138    * Adds the sql map config nodelets.
139    */
140   private void addSqlMapConfigNodelets() {
141     parser.addNodelet("/sqlMapConfig/end()", node -> state.getConfig().finalizeSqlMapConfig());
142   }
143 
144   /**
145    * Adds the global prop nodelets.
146    */
147   private void addGlobalPropNodelets() {
148     parser.addNodelet("/sqlMapConfig/properties", node -> {
149       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
150       String resource = attributes.getProperty("resource");
151       String url = attributes.getProperty("url");
152       state.setGlobalProperties(resource, url);
153     });
154   }
155 
156   /**
157    * Adds the settings nodelets.
158    */
159   private void addSettingsNodelets() {
160     parser.addNodelet("/sqlMapConfig/settings", node -> {
161       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
162       SqlMapConfiguration config = state.getConfig();
163 
164       String classInfoCacheEnabledAttr = attributes.getProperty("classInfoCacheEnabled");
165       boolean classInfoCacheEnabled = classInfoCacheEnabledAttr == null || "true".equals(classInfoCacheEnabledAttr);
166       config.setClassInfoCacheEnabled(classInfoCacheEnabled);
167 
168       String lazyLoadingEnabledAttr = attributes.getProperty("lazyLoadingEnabled");
169       boolean lazyLoadingEnabled = lazyLoadingEnabledAttr == null || "true".equals(lazyLoadingEnabledAttr);
170       config.setLazyLoadingEnabled(lazyLoadingEnabled);
171 
172       String statementCachingEnabledAttr = attributes.getProperty("statementCachingEnabled");
173       boolean statementCachingEnabled = statementCachingEnabledAttr == null
174           || "true".equals(statementCachingEnabledAttr);
175       config.setStatementCachingEnabled(statementCachingEnabled);
176 
177       String cacheModelsEnabledAttr = attributes.getProperty("cacheModelsEnabled");
178       boolean cacheModelsEnabled = cacheModelsEnabledAttr == null || "true".equals(cacheModelsEnabledAttr);
179       config.setCacheModelsEnabled(cacheModelsEnabled);
180 
181       String enhancementEnabledAttr = attributes.getProperty("enhancementEnabled");
182       boolean enhancementEnabled = enhancementEnabledAttr == null || "true".equals(enhancementEnabledAttr);
183       config.setEnhancementEnabled(enhancementEnabled);
184 
185       String useColumnLabelAttr = attributes.getProperty("useColumnLabel");
186       boolean useColumnLabel = useColumnLabelAttr == null || "true".equals(useColumnLabelAttr);
187       config.setUseColumnLabel(useColumnLabel);
188 
189       String forceMultipleResultSetSupportAttr = attributes.getProperty("forceMultipleResultSetSupport");
190       boolean forceMultipleResultSetSupport = "true".equals(forceMultipleResultSetSupportAttr);
191       config.setForceMultipleResultSetSupport(forceMultipleResultSetSupport);
192 
193       String defaultTimeoutAttr = attributes.getProperty("defaultStatementTimeout");
194       Integer defaultTimeout = defaultTimeoutAttr == null ? null : Integer.valueOf(defaultTimeoutAttr);
195       config.setDefaultStatementTimeout(defaultTimeout);
196 
197       String useStatementNamespacesAttr = attributes.getProperty("useStatementNamespaces");
198       boolean useStatementNamespaces = "true".equals(useStatementNamespacesAttr);
199       state.setUseStatementNamespaces(useStatementNamespaces);
200     });
201   }
202 
203   /**
204    * Adds the type alias nodelets.
205    */
206   private void addTypeAliasNodelets() {
207     parser.addNodelet("/sqlMapConfig/typeAlias", node -> {
208       Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
209       String alias = prop.getProperty("alias");
210       String type = prop.getProperty("type");
211       state.getConfig().getTypeHandlerFactory().putTypeAlias(alias, type);
212     });
213   }
214 
215   /**
216    * Adds the type handler nodelets.
217    */
218   private void addTypeHandlerNodelets() {
219     parser.addNodelet("/sqlMapConfig/typeHandler", node -> {
220       Properties prop = NodeletUtils.parseAttributes(node, state.getGlobalProps());
221       String jdbcType = prop.getProperty("jdbcType");
222       String javaType = prop.getProperty("javaType");
223       String callback = prop.getProperty("callback");
224 
225       javaType = state.getConfig().getTypeHandlerFactory().resolveAlias(javaType);
226       callback = state.getConfig().getTypeHandlerFactory().resolveAlias(callback);
227 
228       state.getConfig().newTypeHandler(Resources.classForName(javaType), jdbcType, Resources.instantiate(callback));
229     });
230   }
231 
232   /**
233    * Adds the transaction manager nodelets.
234    */
235   private void addTransactionManagerNodelets() {
236     parser.addNodelet("/sqlMapConfig/transactionManager/property", node -> {
237       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
238       String name = attributes.getProperty("name");
239       String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
240       state.getTxProps().setProperty(name, value);
241     });
242     parser.addNodelet("/sqlMapConfig/transactionManager/end()", node -> {
243       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
244       String type = attributes.getProperty("type");
245       boolean commitRequired = "true".equals(attributes.getProperty("commitRequired"));
246 
247       state.getConfig().getErrorContext().setActivity("configuring the transaction manager");
248       type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
249       TransactionManager txManager;
250       try {
251         state.getConfig().getErrorContext().setMoreInfo("Check the transaction manager type or class.");
252         TransactionConfig config = (TransactionConfig) Resources.instantiate(type);
253         config.setDataSource(state.getDataSource());
254         state.getConfig().getErrorContext().setMoreInfo("Check the transaction manager properties or configuration.");
255         config.setProperties(state.getTxProps());
256         config.setForceCommit(commitRequired);
257         config.setDataSource(state.getDataSource());
258         state.getConfig().getErrorContext().setMoreInfo(null);
259         txManager = new TransactionManager(config);
260       } catch (Exception e) {
261         if (e instanceof SqlMapException) {
262           throw (SqlMapException) e;
263         }
264         throw new SqlMapException(
265             "Error initializing TransactionManager.  Could not instantiate TransactionConfig.  Cause: " + e, e);
266       }
267       state.getConfig().setTransactionManager(txManager);
268     });
269     parser.addNodelet("/sqlMapConfig/transactionManager/dataSource/property", node -> {
270       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
271       String name = attributes.getProperty("name");
272       String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
273       state.getDsProps().setProperty(name, value);
274     });
275     parser.addNodelet("/sqlMapConfig/transactionManager/dataSource/end()", node -> {
276       state.getConfig().getErrorContext().setActivity("configuring the data source");
277 
278       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
279 
280       String type = attributes.getProperty("type");
281       Properties props = state.getDsProps();
282 
283       type = state.getConfig().getTypeHandlerFactory().resolveAlias(type);
284       try {
285         state.getConfig().getErrorContext().setMoreInfo("Check the data source type or class.");
286         DataSourceFactory dsFactory = (DataSourceFactory) Resources.instantiate(type);
287         state.getConfig().getErrorContext().setMoreInfo("Check the data source properties or configuration.");
288         dsFactory.initialize(props);
289         state.setDataSource(dsFactory.getDataSource());
290         state.getConfig().getErrorContext().setMoreInfo(null);
291       } catch (Exception e) {
292         if (e instanceof SqlMapException) {
293           throw (SqlMapException) e;
294         }
295         throw new SqlMapException(
296             "Error initializing DataSource.  Could not instantiate DataSourceFactory.  Cause: " + e, e);
297       }
298     });
299   }
300 
301   /**
302    * Adds the sql map nodelets.
303    */
304   protected void addSqlMapNodelets() {
305     parser.addNodelet("/sqlMapConfig/sqlMap", node -> {
306       state.getConfig().getErrorContext().setActivity("loading the SQL Map resource");
307 
308       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
309 
310       String resource = attributes.getProperty("resource");
311       String url = attributes.getProperty("url");
312 
313       if (usingStreams) {
314         InputStream inputStream = null;
315         if (resource != null) {
316           state.getConfig().getErrorContext().setResource(resource);
317           inputStream = Resources.getResourceAsStream(resource);
318         } else if (url != null) {
319           state.getConfig().getErrorContext().setResource(url);
320           inputStream = Resources.getUrlAsStream(url);
321         } else {
322           throw new SqlMapException("The <sqlMap> element requires either a resource or a url attribute.");
323         }
324 
325         new SqlMapParser(state).parse(inputStream);
326       } else {
327         Reader reader = null;
328         if (resource != null) {
329           state.getConfig().getErrorContext().setResource(resource);
330           reader = Resources.getResourceAsReader(resource);
331         } else if (url != null) {
332           state.getConfig().getErrorContext().setResource(url);
333           reader = Resources.getUrlAsReader(url);
334         } else {
335           throw new SqlMapException("The <sqlMap> element requires either a resource or a url attribute.");
336         }
337 
338         new SqlMapParser(state).parse(reader);
339       }
340     });
341   }
342 
343   /**
344    * Adds the result object factory nodelets.
345    */
346   private void addResultObjectFactoryNodelets() {
347     parser.addNodelet("/sqlMapConfig/resultObjectFactory", node -> {
348       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
349       String type = attributes.getProperty("type");
350 
351       state.getConfig().getErrorContext().setActivity("configuring the Result Object Factory");
352       ResultObjectFactory rof;
353       try {
354         rof = (ResultObjectFactory) Resources.instantiate(type);
355         state.getConfig().setResultObjectFactory(rof);
356       } catch (Exception e) {
357         throw new SqlMapException("Error instantiating resultObjectFactory: " + type, e);
358       }
359 
360     });
361     parser.addNodelet("/sqlMapConfig/resultObjectFactory/property", node -> {
362       Properties attributes = NodeletUtils.parseAttributes(node, state.getGlobalProps());
363       String name = attributes.getProperty("name");
364       String value = NodeletUtils.parsePropertyTokens(attributes.getProperty("value"), state.getGlobalProps());
365       state.getConfig().getDelegate().getResultObjectFactory().setProperty(name, value);
366     });
367   }
368 
369 }