1 /*
2  * Copyright 2002-2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.orm.jdo;
18 
19 import javax.jdo.JDODataStoreException;
20 import javax.jdo.JDOException;
21 import javax.jdo.JDOFatalDataStoreException;
22 import javax.jdo.JDOFatalUserException;
23 import javax.jdo.JDOObjectNotFoundException;
24 import javax.jdo.JDOOptimisticVerificationException;
25 import javax.jdo.JDOUserException;
26 import javax.jdo.PersistenceManager;
27 import javax.jdo.PersistenceManagerFactory;
28 import javax.jdo.Query;
29 import javax.sql.DataSource;
30 
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 
34 import org.springframework.core.Ordered;
35 import org.springframework.dao.DataAccessException;
36 import org.springframework.dao.DataAccessResourceFailureException;
37 import org.springframework.jdbc.datasource.DataSourceUtils;
38 import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
39 import org.springframework.jdbc.support.SQLExceptionTranslator;
40 import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;
41 import org.springframework.transaction.support.ResourceHolderSynchronization;
42 import org.springframework.transaction.support.TransactionSynchronizationManager;
43 import org.springframework.util.Assert;
44 
45 /**
46  * Helper class featuring methods for JDO PersistenceManager handling,
47  * allowing for reuse of PersistenceManager instances within transactions.
48  * Also provides support for exception translation.
49  *
50  * <p>Used internally by {@link JdoTemplate}, {@link JdoInterceptor} and
51  * {@link JdoTransactionManager}. Can also be used directly in application code.
52  *
53  * @author Juergen Hoeller
54  * @since 03.06.2003
55  * @see JdoTransactionManager
56  * @see org.springframework.transaction.jta.JtaTransactionManager
57  * @see org.springframework.transaction.support.TransactionSynchronizationManager
58  */
59 public abstract class PersistenceManagerFactoryUtils {
60 
61 	/**
62 	 * Order value for TransactionSynchronization objects that clean up JDO
63 	 * PersistenceManagers. Return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100
64 	 * to execute PersistenceManager cleanup before JDBC Connection cleanup, if any.
65 	 * @see org.springframework.jdbc.datasource.DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER
66 	 */
67 	public static final int PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER =
68 			DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100;
69 
70 	private static final Log logger = LogFactory.getLog(PersistenceManagerFactoryUtils.class);
71 
72 
73 	/**
74 	 * Create an appropriate SQLExceptionTranslator for the given PersistenceManagerFactory.
75 	 * <p>If a DataSource is found, creates a SQLErrorCodeSQLExceptionTranslator for the
76 	 * DataSource; else, falls back to a SQLStateSQLExceptionTranslator.
77 	 * @param connectionFactory the connection factory of the PersistenceManagerFactory
78 	 * (may be <code>null</code>)
79 	 * @return the SQLExceptionTranslator (never <code>null</code>)
80 	 * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory()
81 	 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
82 	 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
83 	 */
newJdbcExceptionTranslator(Object connectionFactory)84 	static SQLExceptionTranslator newJdbcExceptionTranslator(Object connectionFactory) {
85 		// Check for PersistenceManagerFactory's DataSource.
86 		if (connectionFactory instanceof DataSource) {
87 			return new SQLErrorCodeSQLExceptionTranslator((DataSource) connectionFactory);
88 		}
89 		else {
90 			return new SQLStateSQLExceptionTranslator();
91 		}
92 	}
93 
94 	/**
95 	 * Obtain a JDO PersistenceManager via the given factory. Is aware of a
96 	 * corresponding PersistenceManager bound to the current thread,
97 	 * for example when using JdoTransactionManager. Will create a new
98 	 * PersistenceManager else, if "allowCreate" is <code>true</code>.
99 	 * @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 	 */
getPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)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</code>.
124 	 * <p>Same as <code>getPersistenceManager</code>, 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</code>
132 	 * @see #getPersistenceManager(javax.jdo.PersistenceManagerFactory, boolean)
133 	 * @see JdoTransactionManager
134 	 */
doGetPersistenceManager(PersistenceManagerFactory pmf, boolean allowCreate)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</code>)
180 	 * @return whether the PersistenceManager is transactional
181 	 */
isPersistenceManagerTransactional( PersistenceManager pm, PersistenceManagerFactory pmf)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 	 * @param jdoDialect the JdoDialect to use for applying a query timeout
198 	 * (must not be <code>null</code>)
199 	 * @throws JDOException if thrown by JDO methods
200 	 * @see JdoDialect#applyQueryTimeout
201 	 */
applyTransactionTimeout( Query query, PersistenceManagerFactory pmf, JdoDialect jdoDialect)202 	public static void applyTransactionTimeout(
203 			Query query, PersistenceManagerFactory pmf, JdoDialect jdoDialect) throws JDOException {
204 
205 		Assert.notNull(query, "No Query object specified");
206 		PersistenceManagerHolder pmHolder =
207 		    (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(pmf);
208 		if (pmHolder != null && pmHolder.hasTimeout()) {
209 			jdoDialect.applyQueryTimeout(query, pmHolder.getTimeToLiveInSeconds());
210 		}
211 	}
212 
213 	/**
214 	 * Convert the given JDOException to an appropriate exception from the
215 	 * <code>org.springframework.dao</code> hierarchy.
216 	 * <p>The most important cases like object not found or optimistic locking
217 	 * failure are covered here. For more fine-granular conversion, JdoAccessor and
218 	 * JdoTransactionManager support sophisticated translation of exceptions via a
219 	 * JdoDialect.
220 	 * @param ex JDOException that occured
221 	 * @return the corresponding DataAccessException instance
222 	 * @see JdoAccessor#convertJdoAccessException
223 	 * @see JdoTransactionManager#convertJdoAccessException
224 	 * @see JdoDialect#translateException
225 	 */
convertJdoAccessException(JDOException ex)226 	public static DataAccessException convertJdoAccessException(JDOException ex) {
227 		if (ex instanceof JDOObjectNotFoundException) {
228 			throw new JdoObjectRetrievalFailureException((JDOObjectNotFoundException) ex);
229 		}
230 		if (ex instanceof JDOOptimisticVerificationException) {
231 			throw new JdoOptimisticLockingFailureException((JDOOptimisticVerificationException) ex);
232 		}
233 		if (ex instanceof JDODataStoreException) {
234 			return new JdoResourceFailureException((JDODataStoreException) ex);
235 		}
236 		if (ex instanceof JDOFatalDataStoreException) {
237 			return new JdoResourceFailureException((JDOFatalDataStoreException) ex);
238 		}
239 		if (ex instanceof JDOUserException) {
240 			return new JdoUsageException((JDOUserException) ex);
241 		}
242 		if (ex instanceof JDOFatalUserException) {
243 			return new JdoUsageException((JDOFatalUserException) ex);
244 		}
245 		// fallback
246 		return new JdoSystemException(ex);
247 	}
248 
249 	/**
250 	 * Close the given PersistenceManager, created via the given factory,
251 	 * if it is not managed externally (i.e. not bound to the thread).
252 	 * @param pm PersistenceManager to close
253 	 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
254 	 * (can be <code>null</code>)
255 	 */
releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)256 	public static void releasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf) {
257 		try {
258 			doReleasePersistenceManager(pm, pmf);
259 		}
260 		catch (JDOException ex) {
261 			logger.debug("Could not close JDO PersistenceManager", ex);
262 		}
263 		catch (Throwable ex) {
264 			logger.debug("Unexpected exception on closing JDO PersistenceManager", ex);
265 		}
266 	}
267 
268 	/**
269 	 * Actually release a PersistenceManager for the given factory.
270 	 * Same as <code>releasePersistenceManager</code>, but throwing the original JDOException.
271 	 * @param pm PersistenceManager to close
272 	 * @param pmf PersistenceManagerFactory that the PersistenceManager was created with
273 	 * (can be <code>null</code>)
274 	 * @throws JDOException if thrown by JDO methods
275 	 */
doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)276 	public static void doReleasePersistenceManager(PersistenceManager pm, PersistenceManagerFactory pmf)
277 			throws JDOException {
278 
279 		if (pm == null) {
280 			return;
281 		}
282 		// Only release non-transactional PersistenceManagers.
283 		if (!isPersistenceManagerTransactional(pm, pmf)) {
284 			logger.debug("Closing JDO PersistenceManager");
285 			pm.close();
286 		}
287 	}
288 
289 
290 	/**
291 	 * Callback for resource cleanup at the end of a non-JDO transaction
292 	 * (e.g. when participating in a JtaTransactionManager transaction).
293 	 * @see org.springframework.transaction.jta.JtaTransactionManager
294 	 */
295 	private static class PersistenceManagerSynchronization
296 			extends ResourceHolderSynchronization<PersistenceManagerHolder, PersistenceManagerFactory>
297 			implements Ordered {
298 
299 		private final boolean newPersistenceManager;
300 
PersistenceManagerSynchronization( PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager)301 		public PersistenceManagerSynchronization(
302 				PersistenceManagerHolder pmHolder, PersistenceManagerFactory pmf, boolean newPersistenceManager) {
303 			super(pmHolder, pmf);
304 			this.newPersistenceManager = newPersistenceManager;
305 		}
306 
getOrder()307 		public int getOrder() {
308 			return PERSISTENCE_MANAGER_SYNCHRONIZATION_ORDER;
309 		}
310 
311 		@Override
flushResource(PersistenceManagerHolder resourceHolder)312 		public void flushResource(PersistenceManagerHolder resourceHolder) {
313 			try {
314 				resourceHolder.getPersistenceManager().flush();
315 			}
316 			catch (JDOException ex) {
317 				throw convertJdoAccessException(ex);
318 			}
319 		}
320 
321 		@Override
shouldUnbindAtCompletion()322 		protected boolean shouldUnbindAtCompletion() {
323 			return this.newPersistenceManager;
324 		}
325 
326 		@Override
shouldReleaseAfterCompletion(PersistenceManagerHolder resourceHolder)327 		protected boolean shouldReleaseAfterCompletion(PersistenceManagerHolder resourceHolder) {
328 			return !resourceHolder.getPersistenceManager().isClosed();
329 		}
330 
331 		@Override
releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey)332 		protected void releaseResource(PersistenceManagerHolder resourceHolder, PersistenceManagerFactory resourceKey) {
333 			releasePersistenceManager(resourceHolder.getPersistenceManager(), resourceKey);
334 		}
335 	}
336 
337 }
338