View Javadoc
1   /*
2    * Copyright 2004-2024 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.mapping.statement;
17  
18  import com.ibatis.common.io.ReaderInputStream;
19  import com.ibatis.common.jdbc.exception.NestedSQLException;
20  import com.ibatis.sqlmap.client.SqlMapClient;
21  import com.ibatis.sqlmap.client.event.RowHandler;
22  import com.ibatis.sqlmap.engine.cache.CacheKey;
23  import com.ibatis.sqlmap.engine.execution.SqlExecutor;
24  import com.ibatis.sqlmap.engine.impl.SqlMapClientImpl;
25  import com.ibatis.sqlmap.engine.mapping.parameter.ParameterMap;
26  import com.ibatis.sqlmap.engine.mapping.result.ResultMap;
27  import com.ibatis.sqlmap.engine.mapping.sql.Sql;
28  import com.ibatis.sqlmap.engine.scope.ErrorContext;
29  import com.ibatis.sqlmap.engine.scope.StatementScope;
30  import com.ibatis.sqlmap.engine.transaction.Transaction;
31  import com.ibatis.sqlmap.engine.transaction.TransactionException;
32  import com.ibatis.sqlmap.engine.type.DomTypeMarker;
33  import com.ibatis.sqlmap.engine.type.XmlTypeMarker;
34  
35  import java.io.StringReader;
36  import java.sql.Connection;
37  import java.sql.SQLException;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.List;
41  
42  import javax.xml.XMLConstants;
43  import javax.xml.parsers.DocumentBuilder;
44  import javax.xml.parsers.DocumentBuilderFactory;
45  
46  import org.w3c.dom.Document;
47  
48  /**
49   * The Class MappedStatement.
50   */
51  public class MappedStatement {
52  
53    /** The id. */
54    private String id;
55  
56    /** The result set type. */
57    private Integer resultSetType;
58  
59    /** The fetch size. */
60    private Integer fetchSize;
61  
62    /** The result map. */
63    private ResultMap resultMap;
64  
65    /** The parameter map. */
66    private ParameterMap parameterMap;
67  
68    /** The parameter class. */
69    private Class parameterClass;
70  
71    /** The sql. */
72    private Sql sql;
73  
74    /** The base cache key. */
75    private int baseCacheKey;
76  
77    /** The sql map client. */
78    private SqlMapClientImpl sqlMapClient;
79  
80    /** The timeout. */
81    private Integer timeout;
82  
83    /** The additional result maps. */
84    private ResultMap[] additionalResultMaps = new ResultMap[0];
85  
86    /** The execute listeners. */
87    private List executeListeners = new ArrayList();
88  
89    /** The resource. */
90    private String resource;
91  
92    /**
93     * Gets the statement type.
94     *
95     * @return the statement type
96     */
97    public StatementType getStatementType() {
98      return StatementType.UNKNOWN;
99    }
100 
101   /**
102    * Execute update.
103    *
104    * @param statementScope
105    *          the statement scope
106    * @param trans
107    *          the trans
108    * @param parameterObject
109    *          the parameter object
110    *
111    * @return the int
112    *
113    * @throws SQLException
114    *           the SQL exception
115    */
116   public int executeUpdate(StatementScope statementScope, Transaction trans, Object parameterObject)
117       throws SQLException {
118     ErrorContext errorContext = statementScope.getErrorContext();
119     errorContext.setActivity("preparing the mapped statement for execution");
120     errorContext.setObjectId(this.getId());
121     errorContext.setResource(this.getResource());
122 
123     statementScope.getSession().setCommitRequired(true);
124 
125     try {
126       parameterObject = validateParameter(parameterObject);
127 
128       Sql sql = getSql();
129 
130       errorContext.setMoreInfo("Check the parameter map.");
131       ParameterMap parameterMap = sql.getParameterMap(statementScope, parameterObject);
132 
133       errorContext.setMoreInfo("Check the result map.");
134       ResultMap resultMap = sql.getResultMap(statementScope, parameterObject);
135 
136       statementScope.setResultMap(resultMap);
137       statementScope.setParameterMap(parameterMap);
138 
139       int rows = 0;
140 
141       errorContext.setMoreInfo("Check the parameter map.");
142       Object[] parameters = parameterMap.getParameterObjectValues(statementScope, parameterObject);
143 
144       errorContext.setMoreInfo("Check the SQL statement.");
145       String sqlString = sql.getSql(statementScope, parameterObject);
146 
147       errorContext.setActivity("executing mapped statement");
148       errorContext.setMoreInfo("Check the statement or the result map.");
149       rows = sqlExecuteUpdate(statementScope, trans.getConnection(), sqlString, parameters);
150 
151       errorContext.setMoreInfo("Check the output parameters.");
152       if (parameterObject != null) {
153         postProcessParameterObject(statementScope, parameterObject, parameters);
154       }
155 
156       errorContext.reset();
157       sql.cleanup(statementScope);
158       notifyListeners();
159       return rows;
160     } catch (SQLException e) {
161       errorContext.setCause(e);
162       throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e);
163     } catch (Exception e) {
164       errorContext.setCause(e);
165       throw new NestedSQLException(errorContext.toString(), e);
166     }
167   }
168 
169   /**
170    * Execute query for object.
171    *
172    * @param statementScope
173    *          the statement scope
174    * @param trans
175    *          the trans
176    * @param parameterObject
177    *          the parameter object
178    * @param resultObject
179    *          the result object
180    *
181    * @return the object
182    *
183    * @throws SQLException
184    *           the SQL exception
185    */
186   public Object executeQueryForObject(StatementScope statementScope, Transaction trans, Object parameterObject,
187       Object resultObject) throws SQLException {
188     try {
189       Object object = null;
190 
191       DefaultRowHandler rowHandler = new DefaultRowHandler();
192       executeQueryWithCallback(statementScope, trans.getConnection(), parameterObject, resultObject, rowHandler,
193           SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);
194       List list = rowHandler.getList();
195 
196       if (list.size() > 1) {
197         throw new SQLException("Error: executeQueryForObject returned too many results.");
198       } else if (list.size() > 0) {
199         object = list.get(0);
200       }
201 
202       return object;
203     } catch (TransactionException e) {
204       throw new NestedSQLException("Error getting Connection from Transaction.  Cause: " + e, e);
205     }
206   }
207 
208   /**
209    * Execute query for list.
210    *
211    * @param statementScope
212    *          the statement scope
213    * @param trans
214    *          the trans
215    * @param parameterObject
216    *          the parameter object
217    * @param skipResults
218    *          the skip results
219    * @param maxResults
220    *          the max results
221    *
222    * @return the list
223    *
224    * @throws SQLException
225    *           the SQL exception
226    */
227   public List executeQueryForList(StatementScope statementScope, Transaction trans, Object parameterObject,
228       int skipResults, int maxResults) throws SQLException {
229     try {
230       DefaultRowHandler rowHandler = new DefaultRowHandler();
231       executeQueryWithCallback(statementScope, trans.getConnection(), parameterObject, null, rowHandler, skipResults,
232           maxResults);
233       return rowHandler.getList();
234     } catch (TransactionException e) {
235       throw new NestedSQLException("Error getting Connection from Transaction.  Cause: " + e, e);
236     }
237   }
238 
239   /**
240    * Execute query with row handler.
241    *
242    * @param statementScope
243    *          the statement scope
244    * @param trans
245    *          the trans
246    * @param parameterObject
247    *          the parameter object
248    * @param rowHandler
249    *          the row handler
250    *
251    * @throws SQLException
252    *           the SQL exception
253    */
254   public void executeQueryWithRowHandler(StatementScope statementScope, Transaction trans, Object parameterObject,
255       RowHandler rowHandler) throws SQLException {
256     try {
257       executeQueryWithCallback(statementScope, trans.getConnection(), parameterObject, null, rowHandler,
258           SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);
259     } catch (TransactionException e) {
260       throw new NestedSQLException("Error getting Connection from Transaction.  Cause: " + e, e);
261     }
262   }
263 
264   //
265   // PROTECTED METHODS
266   //
267 
268   /**
269    * Execute query with callback.
270    *
271    * @param statementScope
272    *          the statement scope
273    * @param conn
274    *          the conn
275    * @param parameterObject
276    *          the parameter object
277    * @param resultObject
278    *          the result object
279    * @param rowHandler
280    *          the row handler
281    * @param skipResults
282    *          the skip results
283    * @param maxResults
284    *          the max results
285    *
286    * @throws SQLException
287    *           the SQL exception
288    */
289   protected void executeQueryWithCallback(StatementScope statementScope, Connection conn, Object parameterObject,
290       Object resultObject, RowHandler rowHandler, int skipResults, int maxResults) throws SQLException {
291     ErrorContext errorContext = statementScope.getErrorContext();
292     errorContext.setActivity("preparing the mapped statement for execution");
293     errorContext.setObjectId(this.getId());
294     errorContext.setResource(this.getResource());
295 
296     try {
297       parameterObject = validateParameter(parameterObject);
298 
299       Sql sql = getSql();
300 
301       errorContext.setMoreInfo("Check the parameter map.");
302       ParameterMap parameterMap = sql.getParameterMap(statementScope, parameterObject);
303 
304       errorContext.setMoreInfo("Check the result map.");
305       ResultMap resultMap = sql.getResultMap(statementScope, parameterObject);
306 
307       statementScope.setResultMap(resultMap);
308       statementScope.setParameterMap(parameterMap);
309 
310       errorContext.setMoreInfo("Check the parameter map.");
311       Object[] parameters = parameterMap.getParameterObjectValues(statementScope, parameterObject);
312 
313       errorContext.setMoreInfo("Check the SQL statement.");
314       String sqlString = sql.getSql(statementScope, parameterObject);
315 
316       errorContext.setActivity("executing mapped statement");
317       errorContext.setMoreInfo("Check the SQL statement or the result map.");
318       RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
319       sqlExecuteQuery(statementScope, conn, sqlString, parameters, skipResults, maxResults, callback);
320 
321       errorContext.setMoreInfo("Check the output parameters.");
322       if (parameterObject != null) {
323         postProcessParameterObject(statementScope, parameterObject, parameters);
324       }
325 
326       errorContext.reset();
327       sql.cleanup(statementScope);
328       notifyListeners();
329     } catch (SQLException e) {
330       errorContext.setCause(e);
331       throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e);
332     } catch (Exception e) {
333       errorContext.setCause(e);
334       throw new NestedSQLException(errorContext.toString(), e);
335     }
336   }
337 
338   /**
339    * Post process parameter object.
340    *
341    * @param statementScope
342    *          the statement scope
343    * @param parameterObject
344    *          the parameter object
345    * @param parameters
346    *          the parameters
347    */
348   protected void postProcessParameterObject(StatementScope statementScope, Object parameterObject,
349       Object[] parameters) {
350   }
351 
352   /**
353    * Sql execute update.
354    *
355    * @param statementScope
356    *          the statement scope
357    * @param conn
358    *          the conn
359    * @param sqlString
360    *          the sql string
361    * @param parameters
362    *          the parameters
363    *
364    * @return the int
365    *
366    * @throws SQLException
367    *           the SQL exception
368    */
369   protected int sqlExecuteUpdate(StatementScope statementScope, Connection conn, String sqlString, Object[] parameters)
370       throws SQLException {
371     if (statementScope.getSession().isInBatch()) {
372       getSqlExecutor().addBatch(statementScope, conn, sqlString, parameters);
373       return 0;
374     } else {
375       return getSqlExecutor().executeUpdate(statementScope, conn, sqlString, parameters);
376     }
377   }
378 
379   /**
380    * Sql execute query.
381    *
382    * @param statementScope
383    *          the statement scope
384    * @param conn
385    *          the conn
386    * @param sqlString
387    *          the sql string
388    * @param parameters
389    *          the parameters
390    * @param skipResults
391    *          the skip results
392    * @param maxResults
393    *          the max results
394    * @param callback
395    *          the callback
396    *
397    * @throws SQLException
398    *           the SQL exception
399    */
400   protected void sqlExecuteQuery(StatementScope statementScope, Connection conn, String sqlString, Object[] parameters,
401       int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
402     getSqlExecutor().executeQuery(statementScope, conn, sqlString, parameters, skipResults, maxResults, callback);
403   }
404 
405   /**
406    * Validate parameter.
407    *
408    * @param param
409    *          the param
410    *
411    * @return the object
412    *
413    * @throws SQLException
414    *           the SQL exception
415    */
416   protected Object validateParameter(Object param) throws SQLException {
417     Object newParam = param;
418     Class parameterClass = getParameterClass();
419     if (newParam != null && parameterClass != null) {
420       if (DomTypeMarker.class.isAssignableFrom(parameterClass)) {
421         if (XmlTypeMarker.class.isAssignableFrom(parameterClass)) {
422           if (!(newParam instanceof String) && !(newParam instanceof Document)) {
423             throw new SQLException("Invalid parameter object type.  Expected '" + String.class.getName() + "' or '"
424                 + Document.class.getName() + "' but found '" + newParam.getClass().getName() + "'.");
425           }
426           if (!(newParam instanceof Document)) {
427             newParam = stringToDocument((String) newParam);
428           }
429         } else {
430           if (!Document.class.isAssignableFrom(newParam.getClass())) {
431             throw new SQLException("Invalid parameter object type.  Expected '" + Document.class.getName()
432                 + "' but found '" + newParam.getClass().getName() + "'.");
433           }
434         }
435       } else {
436         if (!parameterClass.isAssignableFrom(newParam.getClass())) {
437           throw new SQLException("Invalid parameter object type.  Expected '" + parameterClass.getName()
438               + "' but found '" + newParam.getClass().getName() + "'.");
439         }
440       }
441     }
442     return newParam;
443   }
444 
445   /**
446    * String to document.
447    *
448    * @param s
449    *          the s
450    *
451    * @return the document
452    */
453   private Document stringToDocument(String s) {
454     try {
455       DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
456       documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
457       documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
458       documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
459       DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
460       return documentBuilder.parse(new ReaderInputStream(new StringReader(s)));
461     } catch (Exception e) {
462       throw new RuntimeException("Error occurred.  Cause: " + e, e);
463     }
464   }
465 
466   /**
467    * Gets the id.
468    *
469    * @return the id
470    */
471   public String getId() {
472     return id;
473   }
474 
475   /**
476    * Gets the result set type.
477    *
478    * @return the result set type
479    */
480   public Integer getResultSetType() {
481     return resultSetType;
482   }
483 
484   /**
485    * Sets the result set type.
486    *
487    * @param resultSetType
488    *          the new result set type
489    */
490   public void setResultSetType(Integer resultSetType) {
491     this.resultSetType = resultSetType;
492   }
493 
494   /**
495    * Gets the fetch size.
496    *
497    * @return the fetch size
498    */
499   public Integer getFetchSize() {
500     return fetchSize;
501   }
502 
503   /**
504    * Sets the fetch size.
505    *
506    * @param fetchSize
507    *          the new fetch size
508    */
509   public void setFetchSize(Integer fetchSize) {
510     this.fetchSize = fetchSize;
511   }
512 
513   /**
514    * Sets the id.
515    *
516    * @param id
517    *          the new id
518    */
519   public void setId(String id) {
520     this.id = id;
521   }
522 
523   /**
524    * Gets the sql.
525    *
526    * @return the sql
527    */
528   public Sql getSql() {
529     return sql;
530   }
531 
532   /**
533    * Sets the sql.
534    *
535    * @param sql
536    *          the new sql
537    */
538   public void setSql(Sql sql) {
539     this.sql = sql;
540   }
541 
542   /**
543    * Gets the result map.
544    *
545    * @return the result map
546    */
547   public ResultMap getResultMap() {
548     return resultMap;
549   }
550 
551   /**
552    * Sets the result map.
553    *
554    * @param resultMap
555    *          the new result map
556    */
557   public void setResultMap(ResultMap resultMap) {
558     this.resultMap = resultMap;
559   }
560 
561   /**
562    * Gets the parameter map.
563    *
564    * @return the parameter map
565    */
566   public ParameterMap getParameterMap() {
567     return parameterMap;
568   }
569 
570   /**
571    * Sets the parameter map.
572    *
573    * @param parameterMap
574    *          the new parameter map
575    */
576   public void setParameterMap(ParameterMap parameterMap) {
577     this.parameterMap = parameterMap;
578   }
579 
580   /**
581    * Gets the parameter class.
582    *
583    * @return the parameter class
584    */
585   public Class getParameterClass() {
586     return parameterClass;
587   }
588 
589   /**
590    * Sets the parameter class.
591    *
592    * @param parameterClass
593    *          the new parameter class
594    */
595   public void setParameterClass(Class parameterClass) {
596     this.parameterClass = parameterClass;
597   }
598 
599   /**
600    * Gets the resource.
601    *
602    * @return the resource
603    */
604   public String getResource() {
605     return resource;
606   }
607 
608   /**
609    * Sets the resource.
610    *
611    * @param resource
612    *          the new resource
613    */
614   public void setResource(String resource) {
615     this.resource = resource;
616   }
617 
618   /**
619    * Gets the cache key.
620    *
621    * @param statementScope
622    *          the statement scope
623    * @param parameterObject
624    *          the parameter object
625    *
626    * @return the cache key
627    */
628   public CacheKey getCacheKey(StatementScope statementScope, Object parameterObject) {
629     Sql sql = statementScope.getSql();
630     ParameterMap pmap = sql.getParameterMap(statementScope, parameterObject);
631     CacheKey cacheKey = pmap.getCacheKey(statementScope, parameterObject);
632     cacheKey.update(id);
633 
634     // I am not sure how any clustered cache solution would ever have had any cache hits against
635     // replicated objects. I could not make it happen
636     // The baseCacheKey value which was being used in the update below is consistent across
637     // JVMInstances on the same machine
638     // but it's not consistent across machines, and therefore breaks clustered caching.
639 
640     // What would happen is the cache values were being replicated across machines but there
641     // were never any cache hits for cached objects on
642     // anything but the original machine an object was created on.
643 
644     // After reviewing this implementation I could not figure out why baseCacheKey is used for
645     // this anyway as it's not needed, so I removed it.
646     // The values used from the pmap.getCacheKey, plus id, plus the params below are unique and
647     // the same accross machines, so now I get replicated
648     // cache hits when I force failover in my cluster
649 
650     // I wish I could make a unit test for this, but I can't do it as the old implementaion
651     // works on 1 machine, but fails across machines.
652     // cacheKey.update(baseCacheKey);
653 
654     cacheKey.update(sql.getSql(statementScope, parameterObject)); // Fixes bug 953001
655     return cacheKey;
656   }
657 
658   /**
659    * Sets the base cache key.
660    *
661    * @param base
662    *          the new base cache key
663    */
664   public void setBaseCacheKey(int base) {
665     this.baseCacheKey = base;
666   }
667 
668   /**
669    * Adds the execute listener.
670    *
671    * @param listener
672    *          the listener
673    */
674   public void addExecuteListener(ExecuteListener listener) {
675     executeListeners.add(listener);
676   }
677 
678   /**
679    * Notify listeners.
680    */
681   public void notifyListeners() {
682     for (int i = 0, n = executeListeners.size(); i < n; i++) {
683       ((ExecuteListener) executeListeners.get(i)).onExecuteStatement(this);
684     }
685   }
686 
687   /**
688    * Gets the sql executor.
689    *
690    * @return the sql executor
691    */
692   public SqlExecutor getSqlExecutor() {
693     return sqlMapClient.getSqlExecutor();
694   }
695 
696   /**
697    * Gets the sql map client.
698    *
699    * @return the sql map client
700    */
701   public SqlMapClient getSqlMapClient() {
702     return sqlMapClient;
703   }
704 
705   /**
706    * Sets the sql map client.
707    *
708    * @param sqlMapClient
709    *          the new sql map client
710    */
711   public void setSqlMapClient(SqlMapClient sqlMapClient) {
712     this.sqlMapClient = (SqlMapClientImpl) sqlMapClient;
713   }
714 
715   /**
716    * Inits the request.
717    *
718    * @param statementScope
719    *          the statement scope
720    */
721   public void initRequest(StatementScope statementScope) {
722     statementScope.setStatement(this);
723     statementScope.setParameterMap(parameterMap);
724     statementScope.setResultMap(resultMap);
725     statementScope.setSql(sql);
726   }
727 
728   /**
729    * Gets the timeout.
730    *
731    * @return the timeout
732    */
733   public Integer getTimeout() {
734     return timeout;
735   }
736 
737   /**
738    * Sets the timeout.
739    *
740    * @param timeout
741    *          the new timeout
742    */
743   public void setTimeout(Integer timeout) {
744     this.timeout = timeout;
745   }
746 
747   /**
748    * Adds the result map.
749    *
750    * @param resultMap
751    *          the result map
752    */
753   public void addResultMap(ResultMap resultMap) {
754     List resultMapList = Arrays.asList(additionalResultMaps);
755     resultMapList = new ArrayList(resultMapList);
756     resultMapList.add(resultMap);
757     additionalResultMaps = (ResultMap[]) resultMapList.toArray(new ResultMap[resultMapList.size()]);
758   }
759 
760   /**
761    * Checks for multiple result maps.
762    *
763    * @return true, if successful
764    */
765   public boolean hasMultipleResultMaps() {
766     return additionalResultMaps.length > 0;
767   }
768 
769   /**
770    * Gets the additional result maps.
771    *
772    * @return the additional result maps
773    */
774   public ResultMap[] getAdditionalResultMaps() {
775     return additionalResultMaps;
776   }
777 }