1. /*
  2. * Copyright 2002-2004 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. package org.springframework.orm.jdo;
  17. import javax.jdo.JDOException;
  18. import javax.jdo.JDOFatalException;
  19. import javax.jdo.PersistenceManager;
  20. import javax.jdo.PersistenceManagerFactory;
  21. import javax.sql.DataSource;
  22. import org.springframework.beans.factory.InitializingBean;
  23. import org.springframework.dao.CleanupFailureDataAccessException;
  24. import org.springframework.dao.DataAccessException;
  25. import org.springframework.jdbc.datasource.ConnectionHolder;
  26. import org.springframework.transaction.CannotCreateTransactionException;
  27. import org.springframework.transaction.InvalidIsolationLevelException;
  28. import org.springframework.transaction.InvalidTimeoutException;
  29. import org.springframework.transaction.TransactionDefinition;
  30. import org.springframework.transaction.TransactionSystemException;
  31. import org.springframework.transaction.support.AbstractPlatformTransactionManager;
  32. import org.springframework.transaction.support.DefaultTransactionStatus;
  33. import org.springframework.transaction.support.TransactionSynchronizationManager;
  34. /**
  35. * PlatformTransactionManager implementation for single JDO persistence manager factories.
  36. * Binds a JDO PersistenceManager from the specified factory to the thread, potentially
  37. * allowing for one thread PersistenceManager per factory. PersistenceManagerFactoryUtils
  38. * and JdoTemplate are aware of thread-bound persistence managers and take part in such
  39. * transactions automatically. Using either is required for JDO access code supporting
  40. * this transaction management mechanism.
  41. *
  42. * <p>This implementation is appropriate for applications that solely use JDO for
  43. * transactional data access. JTA resp. JtaTransactionManager is necessary for accessing
  44. * multiple transactional resources. Note that you need to configure your JDO tool
  45. * accordingly to make it participate in JTA transactions. In contrast to Hibernate,
  46. * this cannot be transparently provided by the Spring transaction manager implementation.
  47. *
  48. * @author Juergen Hoeller
  49. * @since 03.06.2003
  50. * @see #setPersistenceManagerFactory
  51. * @see #setDataSource
  52. * @see PersistenceManagerFactoryUtils#getPersistenceManager
  53. * @see PersistenceManagerFactoryUtils#closePersistenceManagerIfNecessary
  54. * @see JdoTemplate#execute
  55. * @see org.springframework.orm.hibernate.HibernateTransactionManager
  56. */
  57. public class JdoTransactionManager extends AbstractPlatformTransactionManager implements InitializingBean {
  58. private PersistenceManagerFactory persistenceManagerFactory;
  59. private DataSource dataSource;
  60. private JdoDialect jdoDialect;
  61. /**
  62. * Create a new JdoTransactionManager instance.
  63. * A PersistenceManagerFactory has to be set to be able to use it.
  64. * @see #setPersistenceManagerFactory
  65. */
  66. public JdoTransactionManager() {
  67. }
  68. /**
  69. * Create a new JdoTransactionManager instance.
  70. * @param pmf PersistenceManagerFactory to manage transactions for
  71. */
  72. public JdoTransactionManager(PersistenceManagerFactory pmf) {
  73. this.persistenceManagerFactory = pmf;
  74. afterPropertiesSet();
  75. }
  76. /**
  77. * Set the PersistenceManagerFactory that this instance should manage transactions for.
  78. */
  79. public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
  80. this.persistenceManagerFactory = pmf;
  81. }
  82. /**
  83. * Return the PersistenceManagerFactory that this instance should manage transactions for.
  84. */
  85. public PersistenceManagerFactory getPersistenceManagerFactory() {
  86. return persistenceManagerFactory;
  87. }
  88. /**
  89. * Set the JDBC DataSource that this instance should manage transactions for.
  90. * The DataSource should match the one used by the JDO PersistenceManagerFactory:
  91. * for example, you could specify the same JNDI DataSource for both.
  92. * <p>If the PersistenceManagerFactory uses a DataSource as connection factory,
  93. * the DataSource will be auto-detected: You can still explictly specify the
  94. * DataSource, but you don't need to in this case.
  95. * <p>A transactional JDBC Connection for this DataSource will be provided to
  96. * application code accessing this DataSource directly via DataSourceUtils
  97. * or JdbcTemplate. The Connection will be taken from the JDO PersistenceManager.
  98. * <p>Note that you need to use a JDO dialect for a specific JDO implementation
  99. * to allow for exposing JDO transactions as JDBC transactions.
  100. * @see #setJdoDialect
  101. * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory
  102. * @see LocalPersistenceManagerFactoryBean#setDataSource
  103. */
  104. public void setDataSource(DataSource dataSource) {
  105. this.dataSource = dataSource;
  106. }
  107. /**
  108. * Return the JDBC DataSource that this instance manages transactions for.
  109. */
  110. public DataSource getDataSource() {
  111. return dataSource;
  112. }
  113. /**
  114. * Set the JDO dialect to use for this transaction manager.
  115. * <p>The dialect object can be used to retrieve the underlying JDBC connection
  116. * and thus allows for exposing JDO transactions as JDBC transactions.
  117. */
  118. public void setJdoDialect(JdoDialect jdoDialect) {
  119. this.jdoDialect = jdoDialect;
  120. }
  121. /**
  122. * Return the JDO dialect to use for this transaction manager.
  123. */
  124. public JdoDialect getJdoDialect() {
  125. return jdoDialect;
  126. }
  127. public void afterPropertiesSet() {
  128. if (this.persistenceManagerFactory == null) {
  129. throw new IllegalArgumentException("persistenceManagerFactory is required");
  130. }
  131. if (this.jdoDialect == null && this.dataSource != null) {
  132. throw new IllegalArgumentException("A jdoDialect is required to expose JDO transactions as JDBC transactions");
  133. }
  134. if (this.jdoDialect != null) {
  135. // check for DataSource as connection factory
  136. Object pmfcf = this.persistenceManagerFactory.getConnectionFactory();
  137. if (pmfcf instanceof DataSource) {
  138. if (this.dataSource == null) {
  139. // use the PersistenceManagerFactory's DataSource for exposing transactions to JDBC code
  140. logger.info("Using DataSource [" + pmfcf + "] from JDO PersistenceManagerFactory for JdoTransactionManager");
  141. this.dataSource = (DataSource) pmfcf;
  142. }
  143. else if (this.dataSource == pmfcf) {
  144. // let the configuration through: it's consistent
  145. }
  146. else {
  147. throw new IllegalArgumentException("Specified dataSource [" + this.dataSource +
  148. "] does not match [" + pmfcf + "] used by the PersistenceManagerFactory");
  149. }
  150. }
  151. }
  152. }
  153. protected Object doGetTransaction() {
  154. if (TransactionSynchronizationManager.hasResource(this.persistenceManagerFactory)) {
  155. logger.debug("Found thread-bound persistence manager for JDO transaction");
  156. PersistenceManagerHolder pmHolder = (PersistenceManagerHolder) TransactionSynchronizationManager.getResource(this.persistenceManagerFactory);
  157. return new JdoTransactionObject(pmHolder);
  158. }
  159. else {
  160. return new JdoTransactionObject();
  161. }
  162. }
  163. protected boolean isExistingTransaction(Object transaction) {
  164. return ((JdoTransactionObject) transaction).hasTransaction();
  165. }
  166. protected void doBegin(Object transaction, TransactionDefinition definition) {
  167. if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
  168. throw new InvalidIsolationLevelException("JdoTransactionManager does not support custom isolation levels");
  169. }
  170. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  171. throw new InvalidTimeoutException("JdoTransactionManager does not support timeouts", definition.getTimeout());
  172. }
  173. if (definition.isReadOnly()) {
  174. logger.warn("JdoTransactionManager does not support read-only transactions: ignoring 'readOnly' hint");
  175. }
  176. JdoTransactionObject txObject = (JdoTransactionObject) transaction;
  177. if (txObject.getPersistenceManagerHolder() == null) {
  178. logger.debug("Opening new persistence manager for JDO transaction");
  179. PersistenceManager pm = PersistenceManagerFactoryUtils.getPersistenceManager(this.persistenceManagerFactory,
  180. true, false);
  181. txObject.setPersistenceManagerHolder(new PersistenceManagerHolder(pm));
  182. }
  183. logger.debug("Beginning JDO transaction");
  184. try {
  185. PersistenceManager pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
  186. pm.currentTransaction().begin();
  187. if (txObject.isNewPersistenceManagerHolder()) {
  188. TransactionSynchronizationManager.bindResource(this.persistenceManagerFactory, txObject.getPersistenceManagerHolder());
  189. }
  190. // register the JDO PersistenceManager's JDBC Connection for the DataSource, if set
  191. if (this.dataSource != null && this.jdoDialect != null) {
  192. ConnectionHolder conHolder = new ConnectionHolder(this.jdoDialect.getJdbcConnection(pm));
  193. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  194. conHolder.setTimeoutInSeconds(definition.getTimeout());
  195. }
  196. TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
  197. }
  198. }
  199. catch (JDOException ex) {
  200. throw new CannotCreateTransactionException("Could not create JDO transaction", ex);
  201. }
  202. }
  203. protected Object doSuspend(Object transaction) {
  204. JdoTransactionObject txObject = (JdoTransactionObject) transaction;
  205. txObject.setPersistenceManagerHolder(null);
  206. PersistenceManagerHolder persistenceManagerHolder =
  207. (PersistenceManagerHolder) TransactionSynchronizationManager.unbindResource(this.persistenceManagerFactory);
  208. ConnectionHolder connectionHolder = null;
  209. if (this.dataSource != null) {
  210. connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(this.dataSource);
  211. }
  212. return new SuspendedResourcesHolder(persistenceManagerHolder, connectionHolder);
  213. }
  214. protected void doResume(Object transaction, Object suspendedResources) {
  215. SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
  216. TransactionSynchronizationManager.bindResource(this.persistenceManagerFactory, resourcesHolder.getPersistenceManagerHolder());
  217. if (this.dataSource != null) {
  218. TransactionSynchronizationManager.bindResource(this.dataSource, resourcesHolder.getConnectionHolder());
  219. }
  220. }
  221. protected boolean isRollbackOnly(Object transaction) {
  222. return ((JdoTransactionObject) transaction).getPersistenceManagerHolder().isRollbackOnly();
  223. }
  224. protected void doCommit(DefaultTransactionStatus status) {
  225. JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
  226. logger.debug("Committing JDO transaction");
  227. try {
  228. txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction().commit();
  229. }
  230. catch (JDOFatalException ex) {
  231. // assumably from commit call to underlying JDBC connection
  232. throw new TransactionSystemException("Could not commit JDO transaction", ex);
  233. }
  234. catch (JDOException ex) {
  235. // assumably failed to flush changes to database
  236. throw convertJdoAccessException(ex);
  237. }
  238. }
  239. protected void doRollback(DefaultTransactionStatus status) {
  240. JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
  241. logger.debug("Rolling back JDO transaction");
  242. try {
  243. txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction().rollback();
  244. }
  245. catch (JDOException ex) {
  246. throw new TransactionSystemException("Could not rollback JDO transaction", ex);
  247. }
  248. }
  249. protected void doSetRollbackOnly(DefaultTransactionStatus status) {
  250. JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
  251. logger.debug("Setting JDO transaction rollback-only");
  252. txObject.getPersistenceManagerHolder().setRollbackOnly();
  253. }
  254. protected void doCleanupAfterCompletion(Object transaction) {
  255. JdoTransactionObject txObject = (JdoTransactionObject) transaction;
  256. // remove the JDBC connection holder from the thread, if set
  257. if (this.dataSource != null) {
  258. TransactionSynchronizationManager.unbindResource(this.dataSource);
  259. }
  260. // remove the persistence manager holder from the thread
  261. if (txObject.isNewPersistenceManagerHolder()) {
  262. TransactionSynchronizationManager.unbindResource(this.persistenceManagerFactory);
  263. try {
  264. PersistenceManagerFactoryUtils.closePersistenceManagerIfNecessary(
  265. txObject.getPersistenceManagerHolder().getPersistenceManager(), this.persistenceManagerFactory);
  266. }
  267. catch (CleanupFailureDataAccessException ex) {
  268. // just log it, to keep a transaction-related exception
  269. logger.error("Could not close JDO persistence manager after transaction", ex);
  270. }
  271. }
  272. else {
  273. logger.debug("Not closing pre-bound JDO persistence manager after transaction");
  274. }
  275. }
  276. /**
  277. * Convert the given JDOException to an appropriate exception from the
  278. * org.springframework.dao hierarchy. Delegates to the JdoDialect if set, falls
  279. * back to PersistenceManagerFactoryUtils' standard exception translation else.
  280. * May be overridden in subclasses.
  281. * @param ex JDOException that occured
  282. * @return the corresponding DataAccessException instance
  283. * @see JdoDialect#translateException
  284. * @see PersistenceManagerFactoryUtils#convertJdoAccessException
  285. */
  286. protected DataAccessException convertJdoAccessException(JDOException ex) {
  287. if (this.jdoDialect != null) {
  288. return this.jdoDialect.translateException(ex);
  289. }
  290. else {
  291. return PersistenceManagerFactoryUtils.convertJdoAccessException(ex);
  292. }
  293. }
  294. /**
  295. * Holder for suspended resources.
  296. * Used internally by doSuspend and doResume.
  297. * @see #doSuspend
  298. * @see #doResume
  299. */
  300. private static class SuspendedResourcesHolder {
  301. private final PersistenceManagerHolder persistenceManagerHolder;
  302. private final ConnectionHolder connectionHolder;
  303. private SuspendedResourcesHolder(PersistenceManagerHolder persistenceManagerHolder,
  304. ConnectionHolder connectionHolder) {
  305. this.persistenceManagerHolder = persistenceManagerHolder;
  306. this.connectionHolder = connectionHolder;
  307. }
  308. private PersistenceManagerHolder getPersistenceManagerHolder() {
  309. return persistenceManagerHolder;
  310. }
  311. private ConnectionHolder getConnectionHolder() {
  312. return connectionHolder;
  313. }
  314. }
  315. }