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 javax.jdo.JDODataStoreException;
020import javax.jdo.JDOException;
021import javax.jdo.JDOFatalDataStoreException;
022import javax.jdo.JDOFatalUserException;
023import javax.jdo.JDOObjectNotFoundException;
024import javax.jdo.JDOOptimisticVerificationException;
025import javax.jdo.JDOUserException;
026import javax.jdo.PersistenceManager;
027import javax.jdo.PersistenceManagerFactory;
028import javax.jdo.Query;
029import javax.sql.DataSource;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033
034import org.springframework.core.Ordered;
035import org.springframework.dao.DataAccessException;
036import org.springframework.dao.DataAccessResourceFailureException;
037import org.springframework.jdbc.datasource.DataSourceUtils;
038import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
039import org.springframework.jdbc.support.SQLExceptionTranslator;
040import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
041import org.springframework.transaction.support.ResourceHolderSynchronization;
042import org.springframework.transaction.support.TransactionSynchronizationManager;
043import org.springframework.util.Assert;
044
045/**
046 * Helper class featuring methods for JDO {@link PersistenceManager} handling,
047 * allowing for reuse of PersistenceManager instances within transactions.
048 * Also provides support for exception translation.
049 *
050 * <p>Used internally by {@link JdoTransactionManager}.
051 * Can also be used directly in application code.
052 *
053 * @author Juergen Hoeller
054 * @since 03.06.2003
055 * @see JdoTransactionManager
056 * @see org.springframework.transaction.jta.JtaTransactionManager
057 * @see org.springframework.transaction.support.TransactionSynchronizationManager
058 */
059public abstract class PersistenceManagerFactoryUtils {
060
061        /**
062         * Order value for TransactionSynchronization objects that clean up JDO
063         * PersistenceManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
064         * to execute PersistenceManager cleanup before JDBC Connection cleanup, if any.
065         * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
066         */
067        public static final int PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER =
068                        DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
069
070        private static final Log logger = LogFactory.getLog(PersistenceManagerFactoryUtils.class);
071
072
073        /**
074         * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory.
075         * <p>If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the
076         * DataSource; else, falls back to a SQLStateSQLExceptionTranslator.
077         * @param connectionFactory the connection factory of the PersistenceManagerFactory
078         * (may be {@code null})
079         * @return the SQLExceptionTranslator (never {@code null})
080         * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
081         * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
082         * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
083         */
084        static SQLExceptionTranslator newJdbcExceptionTranslator(Object connectionFactory) {
085                // Check for PersistenceManagerFactory's DataSource.
086                if (connectionFactory instanceof DataSource) {
087                        return new SQLErrorCodeSQLExceptionTranslator((DataSource) connectionFactory);
088                }
089                else {
090                        return new SQLStateSQLExceptionTranslator();
091                }
092        }
093
094        /**
095         * Obtain a JDO PersistenceManager via the given factory. Is aware of a
096         * corresponding PersistenceManager bound to the current thread,
097         * for example when using JdoTransactionManager. Will create a new
098         * PersistenceManager else, if "allowCreate" is {@code true}.
099         * @param pmf PersistenceManagerFactory to create the PersistenceManager with
100         * @param allowCreate if a non-transactional PersistenceManager should be created
101         * when no transactional PersistenceManager can be found for the current thread
102         * @return the PersistenceManager
103         * @throws DataAccessResourceFailureException if the PersistenceManager couldn't be obtained
104         * @throws IllegalStateException if no thread-bound PersistenceManager found and
105         * "allowCreate" is {@code false}
106         * @see JdoTransactionManager
107         */
108        public static PersistenceManager getPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
109                throws DataAccessResourceFailureException, IllegalStateException {
110
111                try {
112                        return doGetPersistenceManager(pmf, allowCreate);
113                }
114                catch (JDOException ex) {
115                        throw new DataAccessResourceFailureException("Could not obtain JDO PersistenceManager", ex);
116                }
117        }
118
119        /**
120         * Obtain a JDO PersistenceManager via the given factory. Is aware of a
121         * corresponding PersistenceManager bound to the current thread,
122         * for example when using JdoTransactionManager. Will create a new
123         * PersistenceManager else, if "allowCreate" is {@code true}.
124         * <p>Same as {@code getPersistenceManager}, but throwing the original JDOException.
125         * @param pmf PersistenceManagerFactory to create the PersistenceManager with
126         * @param allowCreate if a non-transactional PersistenceManager should be created
127         * when no transactional PersistenceManager can be found for the current thread
128         * @return the PersistenceManager
129         * @throws JDOException if the PersistenceManager couldn't be created
130         * @throws IllegalStateException if no thread-bound PersistenceManager found and
131         * "allowCreate" is {@code false}
132         * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
133         * @see JdoTransactionManager
134         */
135        public static PersistenceManager doGetPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)
136                throws JDOException, IllegalStateException {
137
138                Assert.notNull(pmf, "No PersistenceManagerFactory specified");
139
140                PersistenceManagerHolder pmHolder =
141                                (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
142                if (pmHolder != null) {
143                        if (!pmHolder.isSynchronizedWithTransaction() &&
144                                        TransactionSynchronizationManager.isSynchronizationActive()) {
145                                pmHolder.setSynchronizedWithTransaction(true);
146                                TransactionSynchronizationManager.registerSynchronization(
147                                                new PersistenceManagerSynchronization(pmHolder, pmf, false));
148                        }
149                        return pmHolder.getPersistenceManager();
150                }
151
152                if (!allowCreate && !TransactionSynchronizationManager.isSynchronizationActive()) {
153                        throw new IllegalStateException("No JDO PersistenceManager bound to thread, " +
154                                        "and configuration does not allow creation of non-transactional one here");
155                }
156
157                logger.debug("Opening JDO PersistenceManager");
158                PersistenceManager pm = pmf.getPersistenceManager();
159
160                if (TransactionSynchronizationManager.isSynchronizationActive()) {
161                        logger.debug("Registering transaction synchronization for JDO PersistenceManager");
162                        // Use same PersistenceManager for further JDO actions within the transaction.
163                        // Thread object will get removed by synchronization at transaction completion.
164                        pmHolder = new PersistenceManagerHolder(pm);
165                        pmHolder.setSynchronizedWithTransaction(true);
166                        TransactionSynchronizationManager.registerSynchronization(
167                                        new PersistenceManagerSynchronization(pmHolder, pmf, true));
168                        TransactionSynchronizationManager.bindResource(pmf, pmHolder);
169                }
170
171                return pm;
172        }
173
174        /**
175         * Return whether the given JDO PersistenceManager is transactional, that is,
176         * bound to the current thread by Spring's transaction facilities.
177         * @param pm the JDO PersistenceManager to check
178         * @param pmf JDO PersistenceManagerFactory that the PersistenceManager
179         * was created with (can be {@code null})
180         * @return whether the PersistenceManager is transactional
181         */
182        public static boolean isPersistenceManagerTransactional(
183                        PersistenceManager pm, PersistenceManagerFactory pmf) {
184
185                if (pmf == null) {
186                        return false;
187                }
188                PersistenceManagerHolder pmHolder =
189                                (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
190                return (pmHolder != null && pm == pmHolder.getPersistenceManager());
191        }
192
193        /**
194         * Apply the current transaction timeout, if any, to the given JDO Query object.
195         * @param query the JDO Query object
196         * @param pmf JDO PersistenceManagerFactory that the Query was created for
197         * @throws JDOException if thrown by JDO methods
198         */
199        public static void applyTransactionTimeout(Query query, PersistenceManagerFactory pmf) throws JDOException {
200                Assert.notNull(query, "No Query object specified");
201                PersistenceManagerHolder pmHolder =
202                                (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
203                if (pmHolder != null && pmHolder.hasTimeout() &&
204                                pmf.supportedOptions().contains("javax.jdo.option.DatastoreTimeout")) {
205                        int timeout = (int) pmHolder.getTimeToLiveInMillis();
206                        query.setDatastoreReadTimeoutMillis(timeout);
207                        query.setDatastoreWriteTimeoutMillis(timeout);
208                }
209        }
210
211        /**
212         * Convert the given JDOException to an appropriate exception from the
213         * {@code org.springframework.dao} hierarchy.
214         * <p>The most important cases like object not found or optimistic locking failure
215         * are covered here. For more fine-granular conversion, JdoTransactionManager
216         * supports sophisticated translation of exceptions via a JdoDialect.
217         * @param ex JDOException that occured
218         * @return the corresponding DataAccessException instance
219         * @see JdoTransactionManager#convertJdoAccessException
220         * @see JdoDialect#translateException
221         */
222        public static DataAccessException convertJdoAccessException(JDOException ex) {
223                if (ex instanceof JDOObjectNotFoundException) {
224                        throw new JdoObjectRetrievalFailureException((JDOObjectNotFoundException) ex);
225                }
226                if (ex instanceof JDOOptimisticVerificationException) {
227                        throw new JdoOptimisticLockingFailureException((JDOOptimisticVerificationException) ex);
228                }
229                if (ex instanceof JDODataStoreException) {
230                        return new JdoResourceFailureException((JDODataStoreException) ex);
231                }
232                if (ex instanceof JDOFatalDataStoreException) {
233                        return new JdoResourceFailureException((JDOFatalDataStoreException) ex);
234                }
235                if (ex instanceof JDOUserException) {
236                        return new JdoUsageException((JDOUserException) ex);
237                }
238                if (ex instanceof JDOFatalUserException) {
239                        return new JdoUsageException((JDOFatalUserException) ex);
240                }
241                // fallback
242                return new JdoSystemException(ex);
243        }
244
245        /**
246         * Close the given PersistenceManager, created via the given factory,
247         * if it is not managed externally (i.e. not bound to the thread).
248         * @param pm PersistenceManager to close
249         * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
250         * (can be {@code null})
251         */
252        public static void releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) {
253                try {
254                        doReleasePersistenceManager(pm, pmf);
255                }
256                catch (JDOException ex) {
257                        logger.debug("Could not close JDO PersistenceManager", ex);
258                }
259                catch (Throwable ex) {
260                        logger.debug("Unexpected exception on closing JDO PersistenceManager", ex);
261                }
262        }
263
264        /**
265         * Actually release a PersistenceManager for the given factory.
266         * Same as {@code releasePersistenceManager}, but throwing the original JDOException.
267         * @param pm PersistenceManager to close
268         * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
269         * (can be {@code null})
270         * @throws JDOException if thrown by JDO methods
271         */
272        public static void doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)
273                        throws JDOException {
274
275                if (pm == null) {
276                        return;
277                }
278                // Only release non-transactional PersistenceManagers.
279                if (!isPersistenceManagerTransactional(pm, pmf)) {
280                        logger.debug("Closing JDO PersistenceManager");
281                        pm.close();
282                }
283        }
284
285
286        /**
287         * Callback for resource cleanup at the end of a non-JDO transaction
288         * (e.g. when participating in a JtaTransactionManager transaction).
289         * @see org.springframework.transaction.jta.JtaTransactionManager
290         */
291        private static class PersistenceManagerSynchronization
292                        extends ResourceHolderSynchronization<PersistenceManagerHolder, PersistenceManagerFactory>
293                        implements Ordered {
294
295                private final boolean newPersistenceManager;
296
297                public PersistenceManagerSynchronization(
298                                PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager) {
299                        super(pmHolder, pmf);
300                        this.newPersistenceManager = newPersistenceManager;
301                }
302
303                @Override
304                public int getOrder() {
305                        return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER;
306                }
307
308                @Override
309                public void flushResource(PersistenceManagerHolder resourceHolder) {
310                        try {
311                                resourceHolder.getPersistenceManager().flush();
312                        }
313                        catch (JDOException ex) {
314                                throw convertJdoAccessException(ex);
315                        }
316                }
317
318                @Override
319                protected boolean shouldUnbindAtCompletion() {
320                        return this.newPersistenceManager;
321                }
322
323                @Override
324                protected boolean shouldReleaseAfterCompletion(PersistenceManagerHolder resourceHolder) {
325                        return !resourceHolder.getPersistenceManager().isClosed();
326                }
327
328                @Override
329                protected void releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey) {
330                        releasePersistenceManager(resourceHolder.getPersistenceManager(), resourceKey);
331                }
332        }
333
334}