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