001: /*
002: * Copyright 2002-2006 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 by JdoTemplate, JdoInterceptor, and JdoTransactionManager.
050: * Can also be used directly in application code, e.g. in combination
051: * with JdoInterceptor.
052: *
053: * @author Juergen Hoeller
054: * @since 03.06.2003
055: * @see JdoTemplate
056: * @see JdoInterceptor
057: * @see JdoTransactionManager
058: */
059: public 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 = DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
068:
069: private static final Log logger = LogFactory
070: .getLog(PersistenceManagerFactoryUtils.class);
071:
072: /**
073: * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory.
074: * <p>If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the
075: * DataSource; else, falls back to a SQLStateSQLExceptionTranslator.
076: * @param connectionFactory the connection factory of the PersistenceManagerFactory
077: * (may be <code>null</code>)
078: * @return the SQLExceptionTranslator (never <code>null</code>)
079: * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
080: * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
081: * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
082: */
083: static SQLExceptionTranslator newJdbcExceptionTranslator(
084: Object connectionFactory) {
085: // Check for PersistenceManagerFactory's DataSource.
086: if (connectionFactory instanceof DataSource) {
087: return new SQLErrorCodeSQLExceptionTranslator(
088: (DataSource) connectionFactory);
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</code>.
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</code>
106: * @see JdoTransactionManager
107: */
108: public static PersistenceManager getPersistenceManager(
109: PersistenceManagerFactory pmf, boolean allowCreate)
110: throws DataAccessResourceFailureException,
111: IllegalStateException {
112:
113: try {
114: return doGetPersistenceManager(pmf, allowCreate);
115: } catch (JDOException ex) {
116: throw new DataAccessResourceFailureException(
117: "Could not obtain JDO PersistenceManager", ex);
118: }
119: }
120:
121: /**
122: * Obtain a JDO PersistenceManager via the given factory. Is aware of a
123: * corresponding PersistenceManager bound to the current thread,
124: * for example when using JdoTransactionManager. Will create a new
125: * PersistenceManager else, if "allowCreate" is <code>true</code>.
126: * <p>Same as <code>getPersistenceManager</code>, but throwing the original JDOException.
127: * @param pmf PersistenceManagerFactory to create the PersistenceManager with
128: * @param allowCreate if a non-transactional PersistenceManager should be created
129: * when no transactional PersistenceManager can be found for the current thread
130: * @return the PersistenceManager
131: * @throws JDOException if the PersistenceManager couldn't be created
132: * @throws IllegalStateException if no thread-bound PersistenceManager found and
133: * "allowCreate" is <code>false</code>
134: * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
135: * @see JdoTransactionManager
136: */
137: public static PersistenceManager doGetPersistenceManager(
138: PersistenceManagerFactory pmf, boolean allowCreate)
139: throws JDOException, IllegalStateException {
140:
141: Assert.notNull(pmf, "No PersistenceManagerFactory specified");
142:
143: PersistenceManagerHolder pmHolder = (PersistenceManagerHolder) TransactionSynchronizationManager
144: .getResource(pmf);
145: if (pmHolder != null) {
146: if (!pmHolder.isSynchronizedWithTransaction()
147: && TransactionSynchronizationManager
148: .isSynchronizationActive()) {
149: pmHolder.setSynchronizedWithTransaction(true);
150: TransactionSynchronizationManager
151: .registerSynchronization(new PersistenceManagerSynchronization(
152: pmHolder, pmf, false));
153: }
154: return pmHolder.getPersistenceManager();
155: }
156:
157: if (!allowCreate
158: && !TransactionSynchronizationManager
159: .isSynchronizationActive()) {
160: throw new IllegalStateException(
161: "No JDO PersistenceManager bound to thread, "
162: + "and configuration does not allow creation of non-transactional one here");
163: }
164:
165: logger.debug("Opening JDO PersistenceManager");
166: PersistenceManager pm = pmf.getPersistenceManager();
167:
168: if (TransactionSynchronizationManager.isSynchronizationActive()) {
169: logger
170: .debug("Registering transaction synchronization for JDO PersistenceManager");
171: // Use same PersistenceManager for further JDO actions within the transaction.
172: // Thread object will get removed by synchronization at transaction completion.
173: pmHolder = new PersistenceManagerHolder(pm);
174: pmHolder.setSynchronizedWithTransaction(true);
175: TransactionSynchronizationManager
176: .registerSynchronization(new PersistenceManagerSynchronization(
177: pmHolder, pmf, true));
178: TransactionSynchronizationManager.bindResource(pmf,
179: pmHolder);
180: }
181:
182: return pm;
183: }
184:
185: /**
186: * Return whether the given JDO PersistenceManager is transactional, that is,
187: * bound to the current thread by Spring's transaction facilities.
188: * @param pm the JDO PersistenceManager to check
189: * @param pmf JDO PersistenceManagerFactory that the PersistenceManager
190: * was created with (can be <code>null</code>)
191: * @return whether the PersistenceManager is transactional
192: */
193: public static boolean isPersistenceManagerTransactional(
194: PersistenceManager pm, PersistenceManagerFactory pmf) {
195:
196: if (pmf == null) {
197: return false;
198: }
199: PersistenceManagerHolder pmHolder = (PersistenceManagerHolder) TransactionSynchronizationManager
200: .getResource(pmf);
201: return (pmHolder != null && pm == pmHolder
202: .getPersistenceManager());
203: }
204:
205: /**
206: * Apply the current transaction timeout, if any, to the given JDO Query object.
207: * @param query the JDO Query object
208: * @param pmf JDO PersistenceManagerFactory that the Query was created for
209: * @param jdoDialect the JdoDialect to use for applying a query timeout
210: * (must not be <code>null</code>)
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: }
|