KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > orm > ibatis > SqlMapClientFactoryBean


1 /*
2  * Copyright 2002-2007 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  * http://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
17 package org.springframework.orm.ibatis;
18
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.InputStreamReader JavaDoc;
22 import java.util.Properties JavaDoc;
23
24 import javax.sql.DataSource JavaDoc;
25
26 import com.ibatis.sqlmap.client.SqlMapClient;
27 import com.ibatis.sqlmap.client.SqlMapClientBuilder;
28 import com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient;
29 import com.ibatis.sqlmap.engine.transaction.TransactionConfig;
30 import com.ibatis.sqlmap.engine.transaction.TransactionManager;
31 import com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig;
32
33 import org.springframework.beans.factory.FactoryBean;
34 import org.springframework.beans.factory.InitializingBean;
35 import org.springframework.core.io.Resource;
36 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
37 import org.springframework.jdbc.support.lob.LobHandler;
38 import org.springframework.util.ClassUtils;
39
40 /**
41  * {@link org.springframework.beans.factory.FactoryBean} that creates an
42  * iBATIS {@link com.ibatis.sqlmap.client.SqlMapClient}. This is the usual
43  * way to set up a shared iBATIS SqlMapClient in a Spring application context;
44  * the SqlMapClient can then be passed to iBATIS-based DAOs via dependency
45  * injection.
46  *
47  * <p>Either {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
48  * or {@link org.springframework.transaction.jta.JtaTransactionManager} can be
49  * used for transaction demarcation in combination with a SqlMapClient,
50  * with JTA only necessary for transactions which span multiple databases.
51  *
52  * <p>Allows for specifying a DataSource at the SqlMapClient level. This
53  * is preferable to per-DAO DataSource references, as it allows for lazy
54  * loading and avoids repeated DataSource references in every DAO.
55  *
56  * <p>Note: As of Spring 2.0.2, this class explicitly supports iBATIS 2.3.
57  * Backwards compatibility with iBATIS 2.1 and 2.2 is preserved for the
58  * time being, through corresponding reflective checks.
59  *
60  * @author Juergen Hoeller
61  * @since 24.02.2004
62  * @see #setConfigLocation
63  * @see #setDataSource
64  * @see SqlMapClientTemplate#setSqlMapClient
65  * @see SqlMapClientTemplate#setDataSource
66  */

67 public class SqlMapClientFactoryBean implements FactoryBean, InitializingBean {
68
69     // Determine whether the SqlMapClientBuilder.buildSqlMapClient(InputStream)
70
// method is available, for use in the "buildSqlMapClient" template method.
71
private final static boolean buildSqlMapClientWithInputStreamMethodAvailable =
72             ClassUtils.hasMethod(SqlMapClientBuilder.class, "buildSqlMapClient",
73                     new Class JavaDoc[] {InputStream JavaDoc.class});
74
75     // Determine whether the SqlMapClientBuilder.buildSqlMapClient(InputStream, Properties)
76
// method is available, for use in the "buildSqlMapClient" template method.
77
private final static boolean buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable =
78             ClassUtils.hasMethod(SqlMapClientBuilder.class, "buildSqlMapClient",
79                     new Class JavaDoc[] {InputStream JavaDoc.class, Properties JavaDoc.class});
80
81
82     private static final ThreadLocal JavaDoc configTimeLobHandlerHolder = new ThreadLocal JavaDoc();
83
84     /**
85      * Return the LobHandler for the currently configured iBATIS SqlMapClient,
86      * to be used by TypeHandler implementations like ClobStringTypeHandler.
87      * <p>This instance will be set before initialization of the corresponding
88      * SqlMapClient, and reset immediately afterwards. It is thus only available
89      * during configuration.
90      * @see #setLobHandler
91      * @see org.springframework.orm.ibatis.support.ClobStringTypeHandler
92      * @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler
93      * @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler
94      */

95     public static LobHandler getConfigTimeLobHandler() {
96         return (LobHandler) configTimeLobHandlerHolder.get();
97     }
98
99
100     private Resource configLocation;
101
102     private Properties JavaDoc sqlMapClientProperties;
103
104     private DataSource JavaDoc dataSource;
105
106     private boolean useTransactionAwareDataSource = true;
107
108     private Class JavaDoc transactionConfigClass = ExternalTransactionConfig.class;
109
110     private Properties JavaDoc transactionConfigProperties;
111
112     private LobHandler lobHandler;
113
114     private SqlMapClient sqlMapClient;
115
116
117     public SqlMapClientFactoryBean() {
118         this.transactionConfigProperties = new Properties JavaDoc();
119         this.transactionConfigProperties.setProperty("SetAutoCommitAllowed", "false");
120     }
121
122     /**
123      * Set the location of the iBATIS SqlMapClient config file.
124      * A typical value is "WEB-INF/sql-map-config.xml".
125      */

126     public void setConfigLocation(Resource configLocation) {
127         this.configLocation = configLocation;
128     }
129
130     /**
131      * Set optional properties to be passed into the SqlMapClientBuilder, as
132      * alternative to a <code>&lt;properties&gt;</code> tag in the sql-map-config.xml
133      * file. Will be used to resolve placeholders in the config file.
134      * @see #setConfigLocation
135      * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient(java.io.Reader, java.util.Properties)
136      */

137     public void setSqlMapClientProperties(Properties JavaDoc sqlMapClientProperties) {
138         this.sqlMapClientProperties = sqlMapClientProperties;
139     }
140
141     /**
142      * Set the DataSource to be used by iBATIS SQL Maps. This will be passed to the
143      * SqlMapClient as part of a TransactionConfig instance.
144      * <p>If specified, this will override corresponding settings in the SqlMapClient
145      * properties. Usually, you will specify DataSource and transaction configuration
146      * <i>either</i> here <i>or</i> in SqlMapClient properties.
147      * <p>Specifying a DataSource for the SqlMapClient rather than for each individual
148      * DAO allows for lazy loading, for example when using PaginatedList results.
149      * <p>With a DataSource passed in here, you don't need to specify one for each DAO.
150      * Passing the SqlMapClient to the DAOs is enough, as it already carries a DataSource.
151      * Thus, it's recommended to specify the DataSource at this central location only.
152      * <p>Thanks to Brandon Goodin from the iBATIS team for the hint on how to make
153      * this work with Spring's integration strategy!
154      * @see #setTransactionConfigClass
155      * @see #setTransactionConfigProperties
156      * @see com.ibatis.sqlmap.client.SqlMapClient#getDataSource
157      * @see SqlMapClientTemplate#setDataSource
158      * @see SqlMapClientTemplate#queryForPaginatedList
159      */

160     public void setDataSource(DataSource JavaDoc dataSource) {
161         this.dataSource = dataSource;
162     }
163
164     /**
165      * Set whether to use a transaction-aware DataSource for the SqlMapClient,
166      * i.e. whether to automatically wrap the passed-in DataSource with Spring's
167      * TransactionAwareDataSourceProxy.
168      * <p>Default is "true": When the SqlMapClient performs direct database operations
169      * outside of Spring's SqlMapClientTemplate (for example, lazy loading or direct
170      * SqlMapClient access), it will still participate in active Spring-managed
171      * transactions.
172      * <p>As a further effect, using a transaction-aware DataSource will apply
173      * remaining transaction timeouts to all created JDBC Statements. This means
174      * that all operations performed by the SqlMapClient will automatically
175      * participate in Spring-managed transaction timeouts.
176      * <p>Turn this flag off to get raw DataSource handling, without Spring transaction
177      * checks. Operations on Spring's SqlMapClientTemplate will still detect
178      * Spring-managed transactions, but lazy loading or direct SqlMapClient access won't.
179      * @see #setDataSource
180      * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
181      * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
182      * @see SqlMapClientTemplate
183      * @see com.ibatis.sqlmap.client.SqlMapClient
184      */

185     public void setUseTransactionAwareDataSource(boolean useTransactionAwareDataSource) {
186         this.useTransactionAwareDataSource = useTransactionAwareDataSource;
187     }
188
189     /**
190      * Set the iBATIS TransactionConfig class to use. Default is
191      * <code>com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig</code>.
192      * <p>Will only get applied when using a Spring-managed DataSource.
193      * An instance of this class will get populated with the given DataSource
194      * and initialized with the given properties.
195      * <p>The default ExternalTransactionConfig is appropriate if there is
196      * external transaction management that the SqlMapClient should participate
197      * in: be it Spring transaction management, EJB CMT or plain JTA. This
198      * should be the typical scenario. If there is no active transaction,
199      * SqlMapClient operations will execute SQL statements non-transactionally.
200      * <p>JdbcTransactionConfig or JtaTransactionConfig is only necessary
201      * when using the iBATIS SqlMapTransactionManager API instead of external
202      * transactions. If there is no explicit transaction, SqlMapClient operations
203      * will automatically start a transaction for their own scope (in contrast
204      * to the external transaction mode, see above).
205      * <p><b>It is strongly recommended to use iBATIS SQL Maps with Spring
206      * transaction management (or EJB CMT).</b> In this case, the default
207      * ExternalTransactionConfig is fine. Lazy loading and SQL Maps operations
208      * without explicit transaction demarcation will execute non-transactionally.
209      * <p>Even with Spring transaction management, it might be desirable to
210      * specify JdbcTransactionConfig: This will still participate in existing
211      * Spring-managed transactions, but lazy loading and operations without
212      * explicit transaction demaration will execute in their own auto-started
213      * transactions. However, this is usually not necessary.
214      * @see #setDataSource
215      * @see #setTransactionConfigProperties
216      * @see com.ibatis.sqlmap.engine.transaction.TransactionConfig
217      * @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig
218      * @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig
219      * @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
220      * @see com.ibatis.sqlmap.client.SqlMapTransactionManager
221          */

222     public void setTransactionConfigClass(Class JavaDoc transactionConfigClass) {
223         if (transactionConfigClass == null || !TransactionConfig.class.isAssignableFrom(transactionConfigClass)) {
224             throw new IllegalArgumentException JavaDoc("Invalid transactionConfigClass: does not implement " +
225                     "com.ibatis.sqlmap.engine.transaction.TransactionConfig");
226         }
227         this.transactionConfigClass = transactionConfigClass;
228     }
229
230     /**
231      * Set properties to be passed to the TransactionConfig instance used
232      * by this SqlMapClient. Supported properties depend on the concrete
233      * TransactionConfig implementation used:
234      * <p><ul>
235      * <li><b>ExternalTransactionConfig</b> supports "DefaultAutoCommit"
236      * (default: false) and "SetAutoCommitAllowed" (default: true).
237      * Note that Spring uses SetAutoCommitAllowed = false as default,
238      * in contrast to the iBATIS default, to always keep the original
239      * autoCommit value as provided by the connection pool.
240      * <li><b>JdbcTransactionConfig</b> does not supported any properties.
241      * <li><b>JtaTransactionConfig</b> supports "UserTransaction"
242      * (no default), specifying the JNDI location of the JTA UserTransaction
243      * (usually "java:comp/UserTransaction").
244      * </ul>
245      * @see com.ibatis.sqlmap.engine.transaction.TransactionConfig#initialize
246      * @see com.ibatis.sqlmap.engine.transaction.external.ExternalTransactionConfig
247      * @see com.ibatis.sqlmap.engine.transaction.jdbc.JdbcTransactionConfig
248      * @see com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
249      */

250     public void setTransactionConfigProperties(Properties JavaDoc transactionConfigProperties) {
251         this.transactionConfigProperties = transactionConfigProperties;
252     }
253
254     /**
255      * Set the LobHandler to be used by the SqlMapClient.
256      * Will be exposed at config time for TypeHandler implementations.
257      * @see #getConfigTimeLobHandler
258      * @see com.ibatis.sqlmap.engine.type.TypeHandler
259      * @see org.springframework.orm.ibatis.support.ClobStringTypeHandler
260      * @see org.springframework.orm.ibatis.support.BlobByteArrayTypeHandler
261      * @see org.springframework.orm.ibatis.support.BlobSerializableTypeHandler
262      */

263     public void setLobHandler(LobHandler lobHandler) {
264         this.lobHandler = lobHandler;
265     }
266
267
268     public void afterPropertiesSet() throws Exception JavaDoc {
269         if (this.configLocation == null) {
270             throw new IllegalArgumentException JavaDoc("configLocation is required");
271         }
272
273         if (this.lobHandler != null) {
274             // Make given LobHandler available for SqlMapClient configuration.
275
// Do early because because mapping resource might refer to custom types.
276
configTimeLobHandlerHolder.set(this.lobHandler);
277         }
278
279         try {
280             this.sqlMapClient = buildSqlMapClient(this.configLocation, this.sqlMapClientProperties);
281
282             // Tell the SqlMapClient to use the given DataSource, if any.
283
if (this.dataSource != null) {
284                 TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
285                 DataSource JavaDoc dataSourceToUse = this.dataSource;
286                 if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
287                     dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
288                 }
289                 transactionConfig.setDataSource(dataSourceToUse);
290                 transactionConfig.initialize(this.transactionConfigProperties);
291                 applyTransactionConfig(this.sqlMapClient, transactionConfig);
292             }
293         }
294
295         finally {
296             if (this.lobHandler != null) {
297                 // Reset LobHandler holder.
298
configTimeLobHandlerHolder.set(null);
299             }
300         }
301     }
302
303     /**
304      * Build a SqlMapClient instance based on the given standard configuration.
305      * <p>The default implementation uses the standard iBATIS {@link SqlMapClientBuilder}
306      * API to build a SqlMapClient instance based on an InputStream (if possible,
307      * on iBATIS 2.3 and higher) or on a Reader (on iBATIS up to version 2.2).
308      * @param configLocation the config file to load from
309      * @param properties the SqlMapClient properties (if any)
310      * @return the SqlMapClient instance (never <code>null</code>)
311      * @throws IOException if loading the config file failed
312      * @see com.ibatis.sqlmap.client.SqlMapClientBuilder#buildSqlMapClient
313      */

314     protected SqlMapClient buildSqlMapClient(Resource configLocation, Properties JavaDoc properties) throws IOException JavaDoc {
315         InputStream JavaDoc is = configLocation.getInputStream();
316         if (properties != null) {
317             if (buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable) {
318                 return SqlMapClientBuilder.buildSqlMapClient(is, properties);
319             }
320             else {
321                 return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader JavaDoc(is), properties);
322             }
323         }
324         else {
325             if (buildSqlMapClientWithInputStreamMethodAvailable) {
326                 return SqlMapClientBuilder.buildSqlMapClient(is);
327             }
328             else {
329                 return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader JavaDoc(is));
330             }
331         }
332     }
333
334     /**
335      * Apply the given iBATIS TransactionConfig to the SqlMapClient.
336      * <p>The default implementation casts to ExtendedSqlMapClient, retrieves the maximum
337      * number of concurrent transactions from the SqlMapExecutorDelegate, and sets
338      * an iBATIS TransactionManager with the given TransactionConfig.
339      * @param sqlMapClient the SqlMapClient to apply the TransactionConfig to
340      * @param transactionConfig the iBATIS TransactionConfig to apply
341      * @see com.ibatis.sqlmap.engine.impl.ExtendedSqlMapClient
342      * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#getMaxTransactions
343      * @see com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate#setTxManager
344      */

345     protected void applyTransactionConfig(SqlMapClient sqlMapClient, TransactionConfig transactionConfig) {
346         if (!(this.sqlMapClient instanceof ExtendedSqlMapClient)) {
347             throw new IllegalArgumentException JavaDoc(
348                     "Cannot set TransactionConfig with DataSource for SqlMapClient if not of type " +
349                     "ExtendedSqlMapClient: " + this.sqlMapClient);
350         }
351         ExtendedSqlMapClient extendedClient = (ExtendedSqlMapClient) this.sqlMapClient;
352         transactionConfig.setMaximumConcurrentTransactions(extendedClient.getDelegate().getMaxTransactions());
353         extendedClient.getDelegate().setTxManager(new TransactionManager(transactionConfig));
354     }
355
356
357     public Object JavaDoc getObject() {
358         return this.sqlMapClient;
359     }
360
361     public Class JavaDoc getObjectType() {
362         return (this.sqlMapClient != null ? this.sqlMapClient.getClass() : SqlMapClient.class);
363     }
364
365     public boolean isSingleton() {
366         return true;
367     }
368
369 }
370
Popular Tags