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