001/*
002 * Copyright 2002-2013 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 *      https://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
017package org.springframework.orm.jdo;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021import javax.jdo.Constants;
022import javax.jdo.JDOException;
023import javax.jdo.PersistenceManager;
024import javax.jdo.Transaction;
025
026import org.springframework.dao.DataAccessException;
027import org.springframework.dao.support.PersistenceExceptionTranslator;
028import org.springframework.jdbc.datasource.ConnectionHandle;
029import org.springframework.jdbc.support.JdbcUtils;
030import org.springframework.jdbc.support.SQLExceptionTranslator;
031import org.springframework.transaction.TransactionDefinition;
032import org.springframework.transaction.TransactionException;
033
034/**
035 * Default implementation of the {@link JdoDialect} interface.
036 * As of Spring 4.0, designed for JDO 3.0 (or rather, semantics beyond JDO 3.0).
037 * Used as default dialect by {@link JdoTransactionManager}.
038 *
039 * <p>Simply begins a standard JDO transaction in {@code beginTransaction}.
040 * Returns a handle for a JDO DataStoreConnection on {@code getJdbcConnection}.
041 * Calls the corresponding JDO PersistenceManager operation on {@code flush}
042 * Uses a Spring SQLExceptionTranslator for exception translation, if applicable.
043 *
044 * <p>Note that, even with JDO 3.0, vendor-specific subclasses are still necessary
045 * for special transaction semantics and more sophisticated exception translation.
046 * Furthermore, vendor-specific subclasses are encouraged to expose the native JDBC
047 * Connection on {@code getJdbcConnection}, rather than JDO 3.0's wrapper handle.
048 *
049 * <p>This class also implements the PersistenceExceptionTranslator interface,
050 * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor,
051 * for AOP-based translation of native exceptions to Spring DataAccessExceptions.
052 * Hence, the presence of a standard DefaultJdoDialect bean automatically enables
053 * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions.
054 *
055 * @author Juergen Hoeller
056 * @since 1.1
057 * @see #setJdbcExceptionTranslator
058 * @see JdoTransactionManager#setJdoDialect
059 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
060 */
061public class DefaultJdoDialect implements JdoDialect, PersistenceExceptionTranslator {
062
063        private SQLExceptionTranslator jdbcExceptionTranslator;
064
065
066        /**
067         * Create a new DefaultJdoDialect.
068         */
069        public DefaultJdoDialect() {
070        }
071
072        /**
073         * Create a new DefaultJdoDialect.
074         * @param connectionFactory the connection factory of the JDO PersistenceManagerFactory,
075         * which is used to initialize the default JDBC exception translator
076         * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
077         * @see PersistenceManagerFactoryUtils#newJdbcExceptionTranslator(Object)
078         */
079        public DefaultJdoDialect(Object connectionFactory) {
080                this.jdbcExceptionTranslator = PersistenceManagerFactoryUtils.newJdbcExceptionTranslator(connectionFactory);
081        }
082
083        /**
084         * Set the JDBC exception translator for this dialect.
085         * <p>Applied to any SQLException root cause of a JDOException, if specified.
086         * The default is to rely on the JDO provider's native exception translation.
087         * @param jdbcExceptionTranslator exception translator
088         * @see java.sql.SQLException
089         * @see javax.jdo.JDOException#getCause()
090         * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
091         * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
092         */
093        public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
094                this.jdbcExceptionTranslator = jdbcExceptionTranslator;
095        }
096
097        /**
098         * Return the JDBC exception translator for this dialect, if any.
099         */
100        public SQLExceptionTranslator getJdbcExceptionTranslator() {
101                return this.jdbcExceptionTranslator;
102        }
103
104
105        //-------------------------------------------------------------------------
106        // Hooks for transaction management (used by JdoTransactionManager)
107        //-------------------------------------------------------------------------
108
109        /**
110         * This implementation invokes the standard JDO {@link Transaction#begin()}
111         * method and also {@link Transaction#setIsolationLevel(String)} if necessary.
112         * @see javax.jdo.Transaction#begin
113         * @see org.springframework.transaction.InvalidIsolationLevelException
114         */
115        @Override
116        public Object beginTransaction(Transaction transaction, TransactionDefinition definition)
117                        throws JDOException, SQLException, TransactionException {
118
119                String jdoIsolationLevel = getJdoIsolationLevel(definition);
120                if (jdoIsolationLevel != null) {
121                        transaction.setIsolationLevel(jdoIsolationLevel);
122                }
123                transaction.begin();
124                return null;
125        }
126
127        /**
128         * Determine the JDO isolation level String to use for the given
129         * Spring transaction definition.
130         * @param definition the Spring transaction definition
131         * @return the corresponding JDO isolation level String, or {@code null}
132         * to indicate that no isolation level should be set explicitly
133         * @see Transaction#setIsolationLevel(String)
134         * @see Constants#TX_SERIALIZABLE
135         * @see Constants#TX_REPEATABLE_READ
136         * @see Constants#TX_READ_COMMITTED
137         * @see Constants#TX_READ_UNCOMMITTED
138         */
139        protected String getJdoIsolationLevel(TransactionDefinition definition) {
140                switch (definition.getIsolationLevel()) {
141                        case TransactionDefinition.ISOLATION_SERIALIZABLE:
142                                return Constants.TX_SERIALIZABLE;
143                        case TransactionDefinition.ISOLATION_REPEATABLE_READ:
144                                return Constants.TX_REPEATABLE_READ;
145                        case TransactionDefinition.ISOLATION_READ_COMMITTED:
146                                return Constants.TX_READ_COMMITTED;
147                        case TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
148                                return Constants.TX_READ_UNCOMMITTED;
149                        default:
150                                return null;
151                }
152        }
153
154        /**
155         * This implementation does nothing, as the default beginTransaction implementation
156         * does not require any cleanup.
157         * @see #beginTransaction
158         */
159        @Override
160        public void cleanupTransaction(Object transactionData) {
161        }
162
163        /**
164         * This implementation returns a DataStoreConnectionHandle for JDO.
165         * <p><b>NOTE:</b> A JDO DataStoreConnection is always a wrapper,
166         * never the native JDBC Connection. If you need access to the native JDBC
167         * Connection (or the connection pool handle, to be unwrapped via a Spring
168         * NativeJdbcExtractor), override this method to return the native
169         * Connection through the corresponding vendor-specific mechanism.
170         * <p>A JDO DataStoreConnection is only "borrowed" from the PersistenceManager:
171         * it needs to be returned as early as possible. Effectively, JDO requires the
172         * fetched Connection to be closed before continuing PersistenceManager work.
173         * For this reason, the exposed ConnectionHandle eagerly releases its JDBC
174         * Connection at the end of each JDBC data access operation (that is, on
175         * {@code DataSourceUtils.releaseConnection}).
176         * @see javax.jdo.PersistenceManager#getDataStoreConnection()
177         * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor
178         * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
179         */
180        @Override
181        public ConnectionHandle getJdbcConnection(PersistenceManager pm, boolean readOnly)
182                        throws JDOException, SQLException {
183
184                return new DataStoreConnectionHandle(pm);
185        }
186
187        /**
188         * This implementation does nothing, assuming that the Connection
189         * will implicitly be closed with the PersistenceManager.
190         * <p>If the JDO provider returns a Connection handle that it
191         * expects the application to close, the dialect needs to invoke
192         * {@code Connection.close} here.
193         * @see java.sql.Connection#close()
194         */
195        @Override
196        public void releaseJdbcConnection(ConnectionHandle conHandle, PersistenceManager pm)
197                        throws JDOException, SQLException {
198        }
199
200
201        //-----------------------------------------------------------------------------------
202        // Hook for exception translation (used by JdoTransactionManager)
203        //-----------------------------------------------------------------------------------
204
205        /**
206         * Implementation of the PersistenceExceptionTranslator interface,
207         * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor.
208         * <p>Converts the exception if it is a JDOException, using this JdoDialect.
209         * Else returns {@code null} to indicate an unknown exception.
210         * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
211         * @see #translateException
212         */
213        @Override
214        public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
215                if (ex instanceof JDOException) {
216                        return translateException((JDOException) ex);
217                }
218                return null;
219        }
220
221        /**
222         * This implementation delegates to PersistenceManagerFactoryUtils.
223         * @see PersistenceManagerFactoryUtils#convertJdoAccessException
224         */
225        @Override
226        public DataAccessException translateException(JDOException ex) {
227                if (getJdbcExceptionTranslator() != null && ex.getCause() instanceof SQLException) {
228                        return getJdbcExceptionTranslator().translate("JDO operation: " + ex.getMessage(),
229                                        extractSqlStringFromException(ex), (SQLException) ex.getCause());
230                }
231                return PersistenceManagerFactoryUtils.convertJdoAccessException(ex);
232        }
233
234        /**
235         * Template method for extracting a SQL String from the given exception.
236         * <p>Default implementation always returns {@code null}. Can be overridden in
237         * subclasses to extract SQL Strings for vendor-specific exception classes.
238         * @param ex the JDOException, containing a SQLException
239         * @return the SQL String, or {@code null} if none found
240         */
241        protected String extractSqlStringFromException(JDOException ex) {
242                return null;
243        }
244
245
246        /**
247         * ConnectionHandle implementation that fetches a new JDO DataStoreConnection
248         * for every {@code getConnection} call and closes the Connection on
249         * {@code releaseConnection}. This is necessary because JDO requires the
250         * fetched Connection to be closed before continuing PersistenceManager work.
251         * @see javax.jdo.PersistenceManager#getDataStoreConnection()
252         */
253        private static class DataStoreConnectionHandle implements ConnectionHandle {
254
255                private final PersistenceManager persistenceManager;
256
257                public DataStoreConnectionHandle(PersistenceManager persistenceManager) {
258                        this.persistenceManager = persistenceManager;
259                }
260
261                @Override
262                public Connection getConnection() {
263                        return (Connection) this.persistenceManager.getDataStoreConnection();
264                }
265
266                @Override
267                public void releaseConnection(Connection con) {
268                        JdbcUtils.closeConnection(con);
269                }
270        }
271
272}