001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.orm.jdo;
018:
019: import java.sql.Connection;
020: import java.sql.SQLException;
021:
022: import javax.jdo.JDOException;
023: import javax.jdo.PersistenceManager;
024: import javax.jdo.Query;
025: import javax.jdo.Transaction;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.dao.DataAccessException;
031: import org.springframework.dao.support.PersistenceExceptionTranslator;
032: import org.springframework.jdbc.datasource.ConnectionHandle;
033: import org.springframework.jdbc.support.JdbcUtils;
034: import org.springframework.jdbc.support.SQLExceptionTranslator;
035: import org.springframework.transaction.InvalidIsolationLevelException;
036: import org.springframework.transaction.TransactionDefinition;
037: import org.springframework.transaction.TransactionException;
038:
039: /**
040: * Default implementation of the {@link JdoDialect} interface.
041: * Updated to build on JDO 2.0 or higher, as of Spring 2.5.
042: * Used as default dialect by {@link JdoAccessor} and {@link JdoTransactionManager}.
043: *
044: * <p>Simply begins a standard JDO transaction in <code>beginTransaction</code>.
045: * Returns a handle for a JDO2 DataStoreConnection on <code>getJdbcConnection</code>.
046: * Calls the corresponding JDO2 PersistenceManager operation on <code>flush</code>
047: * Ignores a given query timeout in <code>applyQueryTimeout</code>.
048: * Uses a Spring SQLExceptionTranslator for exception translation, if applicable.
049: *
050: * <p>Note that, even with JDO2, vendor-specific subclasses are still necessary
051: * for special transaction semantics and more sophisticated exception translation.
052: * Furthermore, vendor-specific subclasses are encouraged to expose the native JDBC
053: * Connection on <code>getJdbcConnection</code>, rather than JDO2's wrapper handle.
054: *
055: * <p>This class also implements the PersistenceExceptionTranslator interface,
056: * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor,
057: * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
058: * Hence, the presence of a standard DefaultJdoDialect bean automatically enables
059: * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions.
060: *
061: * @author Juergen Hoeller
062: * @since 1.1
063: * @see #setJdbcExceptionTranslator
064: * @see JdoAccessor#setJdoDialect
065: * @see JdoTransactionManager#setJdoDialect
066: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
067: */
068: public class DefaultJdoDialect implements JdoDialect,
069: PersistenceExceptionTranslator {
070:
071: protected final Log logger = LogFactory.getLog(getClass());
072:
073: private SQLExceptionTranslator jdbcExceptionTranslator;
074:
075: /**
076: * Create a new DefaultJdoDialect.
077: */
078: public DefaultJdoDialect() {
079: }
080:
081: /**
082: * Create a new DefaultJdoDialect.
083: * @param connectionFactory the connection factory of the JDO PersistenceManagerFactory,
084: * which is used to initialize the default JDBC exception translator
085: * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
086: * @see PersistenceManagerFactoryUtils#newJdbcExceptionTranslator(Object)
087: */
088: DefaultJdoDialect(Object connectionFactory) {
089: this .jdbcExceptionTranslator = PersistenceManagerFactoryUtils
090: .newJdbcExceptionTranslator(connectionFactory);
091: }
092:
093: /**
094: * Set the JDBC exception translator for this dialect.
095: * <p>Applied to any SQLException root cause of a JDOException, if specified.
096: * The default is to rely on the JDO provider's native exception translation.
097: * @param jdbcExceptionTranslator exception translator
098: * @see java.sql.SQLException
099: * @see javax.jdo.JDOException#getCause()
100: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
101: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
102: */
103: public void setJdbcExceptionTranslator(
104: SQLExceptionTranslator jdbcExceptionTranslator) {
105: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
106: }
107:
108: /**
109: * Return the JDBC exception translator for this dialect, if any.
110: */
111: public SQLExceptionTranslator getJdbcExceptionTranslator() {
112: return this .jdbcExceptionTranslator;
113: }
114:
115: //-------------------------------------------------------------------------
116: // Hooks for transaction management (used by JdoTransactionManager)
117: //-------------------------------------------------------------------------
118:
119: /**
120: * This implementation invokes the standard JDO <code>Transaction.begin</code>
121: * method. Throws an InvalidIsolationLevelException if a non-default isolation
122: * level is set.
123: * @see javax.jdo.Transaction#begin
124: * @see org.springframework.transaction.InvalidIsolationLevelException
125: */
126: public Object beginTransaction(Transaction transaction,
127: TransactionDefinition definition) throws JDOException,
128: SQLException, TransactionException {
129:
130: if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
131: throw new InvalidIsolationLevelException(
132: "Standard JDO does not support custom isolation levels: "
133: + "use a special JdoDialect implementation for your JDO provider");
134: }
135: transaction.begin();
136: return null;
137: }
138:
139: /**
140: * This implementation does nothing, as the default beginTransaction implementation
141: * does not require any cleanup.
142: * @see #beginTransaction
143: */
144: public void cleanupTransaction(Object transactionData) {
145: }
146:
147: /**
148: * This implementation returns a DataStoreConnectionHandle for JDO2,
149: * which will also work on JDO1 until actually accessing the JDBC Connection.
150: * <p>For pre-JDO2 implementations, override this method to return the
151: * Connection through the corresponding vendor-specific mechanism, or <code>null</code>
152: * if the Connection is not retrievable.
153: * <p><b>NOTE:</b> A JDO2 DataStoreConnection is always a wrapper,
154: * never the native JDBC Connection. If you need access to the native JDBC
155: * Connection (or the connection pool handle, to be unwrapped via a Spring
156: * NativeJdbcExtractor), override this method to return the native
157: * Connection through the corresponding vendor-specific mechanism.
158: * <p>A JDO2 DataStoreConnection is only "borrowed" from the PersistenceManager:
159: * it needs to be returned as early as possible. Effectively, JDO2 requires the
160: * fetched Connection to be closed before continuing PersistenceManager work.
161: * For this reason, the exposed ConnectionHandle eagerly releases its JDBC
162: * Connection at the end of each JDBC data access operation (that is, on
163: * <code>DataSourceUtils.releaseConnection</code>).
164: * @see javax.jdo.PersistenceManager#getDataStoreConnection()
165: * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
166: * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
167: */
168: public ConnectionHandle getJdbcConnection(PersistenceManager pm,
169: boolean readOnly) throws JDOException, SQLException {
170:
171: return new DataStoreConnectionHandle(pm);
172: }
173:
174: /**
175: * This implementation does nothing, assuming that the Connection
176: * will implicitly be closed with the PersistenceManager.
177: * <p>If the JDO provider returns a Connection handle that it
178: * expects the application to close, the dialect needs to invoke
179: * <code>Connection.close</code> here.
180: * @see java.sql.Connection#close()
181: */
182: public void releaseJdbcConnection(ConnectionHandle conHandle,
183: PersistenceManager pm) throws JDOException, SQLException {
184: }
185:
186: /**
187: * This implementation delegates to JDO 2.0's <code>flush</code> method.
188: * <p>To be overridden for pre-JDO2 implementations, using the corresponding
189: * vendor-specific mechanism there.
190: * @see javax.jdo.PersistenceManager#flush()
191: */
192: public void flush(PersistenceManager pm) throws JDOException {
193: pm.flush();
194: }
195:
196: /**
197: * This implementation logs a warning that it cannot apply a query timeout.
198: */
199: public void applyQueryTimeout(Query query,
200: int remainingTimeInSeconds) throws JDOException {
201: logger
202: .info("DefaultJdoDialect does not support query timeouts: ignoring remaining transaction time");
203: }
204:
205: //-----------------------------------------------------------------------------------
206: // Hook for exception translation (used by JdoTransactionManager and JdoTemplate)
207: //-----------------------------------------------------------------------------------
208:
209: /**
210: * Implementation of the PersistenceExceptionTranslator interface,
211: * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
212: * <p>Converts the exception if it is a JDOException, using this JdoDialect.
213: * Else returns <code>null</code> to indicate an unknown exception.
214: * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
215: * @see #translateException
216: */
217: public DataAccessException translateExceptionIfPossible(
218: RuntimeException ex) {
219: if (ex instanceof JDOException) {
220: return translateException((JDOException) ex);
221: }
222: return null;
223: }
224:
225: /**
226: * This implementation delegates to PersistenceManagerFactoryUtils.
227: * @see PersistenceManagerFactoryUtils#convertJdoAccessException
228: */
229: public DataAccessException translateException(JDOException ex) {
230: if (getJdbcExceptionTranslator() != null
231: && ex.getCause() instanceof SQLException) {
232: return getJdbcExceptionTranslator().translate(
233: "JDO operation: " + ex.getMessage(),
234: extractSqlStringFromException(ex),
235: (SQLException) ex.getCause());
236: }
237: return PersistenceManagerFactoryUtils
238: .convertJdoAccessException(ex);
239: }
240:
241: /**
242: * Template method for extracting a SQL String from the given exception.
243: * <p>Default implementation always returns <code>null</code>. Can be overridden in
244: * subclasses to extract SQL Strings for vendor-specific exception classes.
245: * @param ex the JDOException, containing a SQLException
246: * @return the SQL String, or <code>null</code> if none found
247: */
248: protected String extractSqlStringFromException(JDOException ex) {
249: return null;
250: }
251:
252: /**
253: * ConnectionHandle implementation that fetches a new JDO2 DataStoreConnection
254: * for every <code>getConnection</code> call and closes the Connection on
255: * <code>releaseConnection</code>. This is necessary because JDO2 requires the
256: * fetched Connection to be closed before continuing PersistenceManager work.
257: * @see javax.jdo.PersistenceManager#getDataStoreConnection()
258: */
259: private static class DataStoreConnectionHandle implements
260: ConnectionHandle {
261:
262: private final PersistenceManager persistenceManager;
263:
264: public DataStoreConnectionHandle(
265: PersistenceManager persistenceManager) {
266: this .persistenceManager = persistenceManager;
267: }
268:
269: public Connection getConnection() {
270: return (Connection) this .persistenceManager
271: .getDataStoreConnection();
272: }
273:
274: public void releaseConnection(Connection con) {
275: JdbcUtils.closeConnection(con);
276: }
277: }
278:
279: }
|