001/*
002 * Copyright 2002-2014 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 *      https://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
017package org.springframework.orm.jdo;
018
019import javax.jdo.JDOException;
020import javax.jdo.PersistenceManager;
021import javax.jdo.PersistenceManagerFactory;
022import javax.jdo.Transaction;
023import javax.sql.DataSource;
024
025import org.springframework.beans.factory.InitializingBean;
026import org.springframework.dao.DataAccessException;
027import org.springframework.jdbc.datasource.ConnectionHandle;
028import org.springframework.jdbc.datasource.ConnectionHolder;
029import org.springframework.jdbc.datasource.JdbcTransactionObjectSupport;
030import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
031import org.springframework.transaction.CannotCreateTransactionException;
032import org.springframework.transaction.IllegalTransactionStateException;
033import org.springframework.transaction.TransactionDefinition;
034import org.springframework.transaction.TransactionException;
035import org.springframework.transaction.TransactionSystemException;
036import org.springframework.transaction.support.AbstractPlatformTransactionManager;
037import org.springframework.transaction.support.DefaultTransactionStatus;
038import org.springframework.transaction.support.DelegatingTransactionDefinition;
039import org.springframework.transaction.support.ResourceTransactionManager;
040import org.springframework.transaction.support.TransactionSynchronizationManager;
041
042/**
043 * {@link org.springframework.transaction.PlatformTransactionManager} implementation for a
044 * single JDO {@link javax.jdo.PersistenceManagerFactory}. Binds a JDO PersistenceManager
045 * from the specified factory to the thread, potentially allowing for one thread-bound
046 * PersistenceManager per factory. {@link PersistenceManagerFactoryUtils} and
047 * {@link org.springframework.orm.jdo.support.SpringPersistenceManagerProxyBean} are aware
048 * of thread-bound persistence managers and participate in such transactions automatically.
049 * Using either of those (or going through a {@link TransactionAwarePersistenceManagerFactoryProxy}
050 * is required for JDO access code supporting this transaction management mechanism.
051 *
052 * <p>This transaction manager is appropriate for applications that use a single
053 * JDO PersistenceManagerFactory for transactional data access. JTA (usually through
054 * {@link org.springframework.transaction.jta.JtaTransactionManager}) is necessary
055 * for accessing multiple transactional resources within the same transaction.
056 * Note that you need to configure your JDO provider accordingly in order to make
057 * it participate in JTA transactions.
058 *
059 * <p>This transaction manager also supports direct DataSource access within a
060 * transaction (i.e. plain JDBC code working with the same DataSource).
061 * This allows for mixing services which access JDO and services which use plain
062 * JDBC (without being aware of JDO)! Application code needs to stick to the
063 * same simple Connection lookup pattern as with
064 * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
065 * (i.e. {@link org.springframework.jdbc.datasource.DataSourceUtils#getConnection}
066 * or going through a
067 * {@link org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy}).
068 *
069 * <p>Note: To be able to register a DataSource's Connection for plain JDBC code,
070 * this instance needs to be aware of the DataSource ({@link #setDataSource}).
071 * The given DataSource should obviously match the one used by the given
072 * PersistenceManagerFactory. This transaction manager will autodetect the DataSource
073 * that acts as "connectionFactory" of the PersistenceManagerFactory, so you usually
074 * don't need to explicitly specify the "dataSource" property.
075 *
076 * <p>This transaction manager supports nested transactions via JDBC 3.0 Savepoints.
077 * The {@link #setNestedTransactionAllowed} "nestedTransactionAllowed"} flag defaults
078 * to "false", though, as nested transactions will just apply to the JDBC Connection,
079 * not to the JDO PersistenceManager and its cached entity objects and related context.
080 * You can manually set the flag to "true" if you want to use nested transactions
081 * for JDBC access code which participates in JDO transactions (provided that your
082 * JDBC driver supports Savepoints). <i>Note that JDO itself does not support
083 * nested transactions! Hence, do not expect JDO access code to semantically
084 * participate in a nested transaction.</i>
085 *
086 * @author Juergen Hoeller
087 * @since 03.06.2003
088 * @see #setPersistenceManagerFactory
089 * @see #setDataSource
090 * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory
091 * @see LocalPersistenceManagerFactoryBean
092 * @see PersistenceManagerFactoryUtils#getPersistenceManager
093 * @see PersistenceManagerFactoryUtils#releasePersistenceManager
094 * @see TransactionAwarePersistenceManagerFactoryProxy
095 * @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
096 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
097 * @see org.springframework.jdbc.core.JdbcTemplate
098 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
099 * @see org.springframework.transaction.jta.JtaTransactionManager
100 */
101@SuppressWarnings("serial")
102public class JdoTransactionManager extends AbstractPlatformTransactionManager
103                implements ResourceTransactionManager, InitializingBean {
104
105        private PersistenceManagerFactory persistenceManagerFactory;
106
107        private DataSource dataSource;
108
109        private boolean autodetectDataSource = true;
110
111        private JdoDialect jdoDialect;
112
113
114        /**
115         * Create a new JdoTransactionManager instance.
116         * A PersistenceManagerFactory has to be set to be able to use it.
117         * @see #setPersistenceManagerFactory
118         */
119        public JdoTransactionManager() {
120        }
121
122        /**
123         * Create a new JdoTransactionManager instance.
124         * @param pmf PersistenceManagerFactory to manage transactions for
125         */
126        public JdoTransactionManager(PersistenceManagerFactory pmf) {
127                this.persistenceManagerFactory = pmf;
128                afterPropertiesSet();
129        }
130
131
132        /**
133         * Set the PersistenceManagerFactory that this instance should manage transactions for.
134         * <p>The PersistenceManagerFactory specified here should be the target
135         * PersistenceManagerFactory to manage transactions for, not a
136         * TransactionAwarePersistenceManagerFactoryProxy. Only data access
137         * code may work with TransactionAwarePersistenceManagerFactoryProxy, while the
138         * transaction manager needs to work on the underlying target PersistenceManagerFactory.
139         * @see TransactionAwarePersistenceManagerFactoryProxy
140         */
141        public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
142                this.persistenceManagerFactory = pmf;
143        }
144
145        /**
146         * Return the PersistenceManagerFactory that this instance should manage transactions for.
147         */
148        public PersistenceManagerFactory getPersistenceManagerFactory() {
149                return this.persistenceManagerFactory;
150        }
151
152        /**
153         * Set the JDBC DataSource that this instance should manage transactions for.
154   * The DataSource should match the one used by the JDO PersistenceManagerFactory:
155         * for example, you could specify the same JNDI DataSource for both.
156         * <p>If the PersistenceManagerFactory uses a DataSource as connection factory,
157         * the DataSource will be autodetected: You can still explicitly specify the
158         * DataSource, but you don't need to in this case.
159         * <p>A transactional JDBC Connection for this DataSource will be provided to
160         * application code accessing this DataSource directly via DataSourceUtils
161         * or JdbcTemplate. The Connection will be taken from the JDO PersistenceManager.
162         * <p>Note that you need to use a JDO dialect for a specific JDO provider to
163         * allow for exposing JDO transactions as JDBC transactions.
164         * <p>The DataSource specified here should be the target DataSource to manage
165         * transactions for, not a TransactionAwareDataSourceProxy. Only data access
166         * code may work with TransactionAwareDataSourceProxy, while the transaction
167         * manager needs to work on the underlying target DataSource. If there's
168         * nevertheless a TransactionAwareDataSourceProxy passed in, it will be
169         * unwrapped to extract its target DataSource.
170         * @see #setAutodetectDataSource
171         * @see #setJdoDialect
172         * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory
173         * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy
174         * @see org.springframework.jdbc.datasource.DataSourceUtils
175         * @see org.springframework.jdbc.core.JdbcTemplate
176         */
177        public void setDataSource(DataSource dataSource) {
178                if (dataSource instanceof TransactionAwareDataSourceProxy) {
179                        // If we got a TransactionAwareDataSourceProxy, we need to perform transactions
180                        // for its underlying target DataSource, else data access code won't see
181                        // properly exposed transactions (i.e. transactions for the target DataSource).
182                        this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
183                }
184                else {
185                        this.dataSource = dataSource;
186                }
187        }
188
189        /**
190         * Return the JDBC DataSource that this instance manages transactions for.
191         */
192        public DataSource getDataSource() {
193                return this.dataSource;
194        }
195
196        /**
197         * Set whether to autodetect a JDBC DataSource used by the JDO PersistenceManagerFactory,
198         * as returned by the {@code getConnectionFactory()} method. Default is "true".
199         * <p>Can be turned off to deliberately ignore an available DataSource,
200         * to not expose JDO transactions as JDBC transactions for that DataSource.
201         * @see #setDataSource
202         * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory
203         */
204        public void setAutodetectDataSource(boolean autodetectDataSource) {
205                this.autodetectDataSource = autodetectDataSource;
206        }
207
208        /**
209         * Set the JDO dialect to use for this transaction manager.
210         * <p>The dialect object can be used to retrieve the underlying JDBC connection
211         * and thus allows for exposing JDO transactions as JDBC transactions.
212         * @see JdoDialect#getJdbcConnection
213         */
214        public void setJdoDialect(JdoDialect jdoDialect) {
215                this.jdoDialect = jdoDialect;
216        }
217
218        /**
219         * Return the JDO dialect to use for this transaction manager.
220         * <p>Creates a default one for the specified PersistenceManagerFactory if none set.
221         */
222        public JdoDialect getJdoDialect() {
223                if (this.jdoDialect == null) {
224                        this.jdoDialect = new DefaultJdoDialect();
225                }
226                return this.jdoDialect;
227        }
228
229        /**
230         * Eagerly initialize the JDO dialect, creating a default one
231         * for the specified PersistenceManagerFactory if none set.
232         * Auto-detect the PersistenceManagerFactory's DataSource, if any.
233         */
234        @Override
235        public void afterPropertiesSet() {
236                if (getPersistenceManagerFactory() == null) {
237                        throw new IllegalArgumentException("Property 'persistenceManagerFactory' is required");
238                }
239                // Build default JdoDialect if none explicitly specified.
240                if (this.jdoDialect == null) {
241                        this.jdoDialect = new DefaultJdoDialect(getPersistenceManagerFactory().getConnectionFactory());
242                }
243
244                // Check for DataSource as connection factory.
245                if (this.autodetectDataSource && getDataSource() == null) {
246                        Object pmfcf = getPersistenceManagerFactory().getConnectionFactory();
247                        if (pmfcf instanceof DataSource) {
248                                // Use the PersistenceManagerFactory's DataSource for exposing transactions to JDBC code.
249                                this.dataSource = (DataSource) pmfcf;
250                                if (logger.isInfoEnabled()) {
251                                        logger.info("Using DataSource [" + this.dataSource +
252                                                        "] of JDO PersistenceManagerFactory for JdoTransactionManager");
253                                }
254                        }
255                }
256        }
257
258
259        @Override
260        public Object getResourceFactory() {
261                return getPersistenceManagerFactory();
262        }
263
264        @Override
265        protected Object doGetTransaction() {
266                JdoTransactionObject txObject = new JdoTransactionObject();
267                txObject.setSavepointAllowed(isNestedTransactionAllowed());
268
269                PersistenceManagerHolder pmHolder = (PersistenceManagerHolder)
270                                TransactionSynchronizationManager.getResource(getPersistenceManagerFactory());
271                if (pmHolder != null) {
272                        if (logger.isDebugEnabled()) {
273                                logger.debug("Found thread-bound PersistenceManager [" +
274                                                pmHolder.getPersistenceManager() + "] for JDO transaction");
275                        }
276                        txObject.setPersistenceManagerHolder(pmHolder, false);
277                }
278
279                if (getDataSource() != null) {
280                        ConnectionHolder conHolder = (ConnectionHolder)
281                                        TransactionSynchronizationManager.getResource(getDataSource());
282                        txObject.setConnectionHolder(conHolder);
283                }
284
285                return txObject;
286        }
287
288        @Override
289        protected boolean isExistingTransaction(Object transaction) {
290                return ((JdoTransactionObject) transaction).hasTransaction();
291        }
292
293        @Override
294        protected void doBegin(Object transaction, TransactionDefinition definition) {
295                JdoTransactionObject txObject = (JdoTransactionObject) transaction;
296
297                if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
298                        throw new IllegalTransactionStateException(
299                                        "Pre-bound JDBC Connection found! JdoTransactionManager does not support " +
300                                        "running within DataSourceTransactionManager if told to manage the DataSource itself. " +
301                                        "It is recommended to use a single JdoTransactionManager for all transactions " +
302                                        "on a single DataSource, no matter whether JDO or JDBC access.");
303                }
304
305                PersistenceManager pm;
306
307                try {
308                        if (txObject.getPersistenceManagerHolder() == null ||
309                                        txObject.getPersistenceManagerHolder().isSynchronizedWithTransaction()) {
310                                PersistenceManager newPm = getPersistenceManagerFactory().getPersistenceManager();
311                                if (logger.isDebugEnabled()) {
312                                        logger.debug("Opened new PersistenceManager [" + newPm + "] for JDO transaction");
313                                }
314                                txObject.setPersistenceManagerHolder(new PersistenceManagerHolder(newPm), true);
315                        }
316
317                        pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
318
319                        // Delegate to JdoDialect for actual transaction begin.
320                        final int timeoutToUse = determineTimeout(definition);
321                        Object transactionData = getJdoDialect().beginTransaction(pm.currentTransaction(),
322                                        new DelegatingTransactionDefinition(definition) {
323                                                @Override
324                                                public int getTimeout() {
325                                                        return timeoutToUse;
326                                                }
327                                        });
328                        txObject.setTransactionData(transactionData);
329
330                        // Register transaction timeout.
331                        if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
332                                txObject.getPersistenceManagerHolder().setTimeoutInSeconds(timeoutToUse);
333                        }
334
335                        // Register the JDO PersistenceManager's JDBC Connection for the DataSource, if set.
336                        if (getDataSource() != null) {
337                                ConnectionHandle conHandle = getJdoDialect().getJdbcConnection(pm, definition.isReadOnly());
338                                if (conHandle != null) {
339                                        ConnectionHolder conHolder = new ConnectionHolder(conHandle);
340                                        if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {
341                                                conHolder.setTimeoutInSeconds(timeoutToUse);
342                                        }
343                                        if (logger.isDebugEnabled()) {
344                                                logger.debug("Exposing JDO transaction as JDBC transaction [" +
345                                                                conHolder.getConnectionHandle() + "]");
346                                        }
347                                        TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
348                                        txObject.setConnectionHolder(conHolder);
349                                }
350                                else {
351                                        if (logger.isDebugEnabled()) {
352                                                logger.debug("Not exposing JDO transaction [" + pm + "] as JDBC transaction because " +
353                                                                "JdoDialect [" + getJdoDialect() + "] does not support JDBC Connection retrieval");
354                                        }
355                                }
356                        }
357
358                        // Bind the persistence manager holder to the thread.
359                        if (txObject.isNewPersistenceManagerHolder()) {
360                                TransactionSynchronizationManager.bindResource(
361                                                getPersistenceManagerFactory(), txObject.getPersistenceManagerHolder());
362                        }
363                        txObject.getPersistenceManagerHolder().setSynchronizedWithTransaction(true);
364                }
365
366                catch (TransactionException ex) {
367                        closePersistenceManagerAfterFailedBegin(txObject);
368                        throw ex;
369                }
370                catch (Throwable ex) {
371                        closePersistenceManagerAfterFailedBegin(txObject);
372                        throw new CannotCreateTransactionException("Could not open JDO PersistenceManager for transaction", ex);
373                }
374        }
375
376        /**
377         * Close the current transaction's EntityManager.
378         * Called after a transaction begin attempt failed.
379         * @param txObject the current transaction
380         */
381        protected void closePersistenceManagerAfterFailedBegin(JdoTransactionObject txObject) {
382                if (txObject.isNewPersistenceManagerHolder()) {
383                        PersistenceManager pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
384                        try {
385                                if (pm.currentTransaction().isActive()) {
386                                        pm.currentTransaction().rollback();
387                                }
388                        }
389                        catch (Throwable ex) {
390                                logger.debug("Could not rollback PersistenceManager after failed transaction begin", ex);
391                        }
392                        finally {
393                                PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory());
394                        }
395                        txObject.setPersistenceManagerHolder(null, false);
396                }
397        }
398
399        @Override
400        protected Object doSuspend(Object transaction) {
401                JdoTransactionObject txObject = (JdoTransactionObject) transaction;
402                txObject.setPersistenceManagerHolder(null, false);
403                PersistenceManagerHolder persistenceManagerHolder = (PersistenceManagerHolder)
404                                TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory());
405                txObject.setConnectionHolder(null);
406                ConnectionHolder connectionHolder = null;
407                if (getDataSource() != null && TransactionSynchronizationManager.hasResource(getDataSource())) {
408                        connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(getDataSource());
409                }
410                return new SuspendedResourcesHolder(persistenceManagerHolder, connectionHolder);
411        }
412
413        @Override
414        protected void doResume(Object transaction, Object suspendedResources) {
415                SuspendedResourcesHolder resourcesHolder = (SuspendedResourcesHolder) suspendedResources;
416                TransactionSynchronizationManager.bindResource(
417                                getPersistenceManagerFactory(), resourcesHolder.getPersistenceManagerHolder());
418                if (getDataSource() != null && resourcesHolder.getConnectionHolder() != null) {
419                        TransactionSynchronizationManager.bindResource(getDataSource(), resourcesHolder.getConnectionHolder());
420                }
421        }
422
423        /**
424         * This implementation returns "true": a JDO commit will properly handle
425         * transactions that have been marked rollback-only at a global level.
426         */
427        @Override
428        protected boolean shouldCommitOnGlobalRollbackOnly() {
429                return true;
430        }
431
432        @Override
433        protected void doCommit(DefaultTransactionStatus status) {
434                JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
435                if (status.isDebug()) {
436                        logger.debug("Committing JDO transaction on PersistenceManager [" +
437                                        txObject.getPersistenceManagerHolder().getPersistenceManager() + "]");
438                }
439                try {
440                        Transaction tx = txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction();
441                        tx.commit();
442                }
443                catch (JDOException ex) {
444                        // Assumably failed to flush changes to database.
445                        throw convertJdoAccessException(ex);
446                }
447        }
448
449        @Override
450        protected void doRollback(DefaultTransactionStatus status) {
451                JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
452                if (status.isDebug()) {
453                        logger.debug("Rolling back JDO transaction on PersistenceManager [" +
454                                        txObject.getPersistenceManagerHolder().getPersistenceManager() + "]");
455                }
456                try {
457                        Transaction tx = txObject.getPersistenceManagerHolder().getPersistenceManager().currentTransaction();
458                        if (tx.isActive()) {
459                                tx.rollback();
460                        }
461                }
462                catch (JDOException ex) {
463                        throw new TransactionSystemException("Could not roll back JDO transaction", ex);
464                }
465        }
466
467        @Override
468        protected void doSetRollbackOnly(DefaultTransactionStatus status) {
469                JdoTransactionObject txObject = (JdoTransactionObject) status.getTransaction();
470                if (status.isDebug()) {
471                        logger.debug("Setting JDO transaction on PersistenceManager [" +
472                                        txObject.getPersistenceManagerHolder().getPersistenceManager() + "] rollback-only");
473                }
474                txObject.setRollbackOnly();
475        }
476
477        @Override
478        protected void doCleanupAfterCompletion(Object transaction) {
479                JdoTransactionObject txObject = (JdoTransactionObject) transaction;
480
481                // Remove the persistence manager holder from the thread.
482                if (txObject.isNewPersistenceManagerHolder()) {
483                        TransactionSynchronizationManager.unbindResource(getPersistenceManagerFactory());
484                }
485                txObject.getPersistenceManagerHolder().clear();
486
487                // Remove the JDBC connection holder from the thread, if exposed.
488                if (txObject.hasConnectionHolder()) {
489                        TransactionSynchronizationManager.unbindResource(getDataSource());
490                        try {
491                                getJdoDialect().releaseJdbcConnection(txObject.getConnectionHolder().getConnectionHandle(),
492                                                txObject.getPersistenceManagerHolder().getPersistenceManager());
493                        }
494                        catch (Throwable ex) {
495                                // Just log it, to keep a transaction-related exception.
496                                logger.debug("Could not release JDBC connection after transaction", ex);
497                        }
498                }
499
500                getJdoDialect().cleanupTransaction(txObject.getTransactionData());
501
502                if (txObject.isNewPersistenceManagerHolder()) {
503                        PersistenceManager pm = txObject.getPersistenceManagerHolder().getPersistenceManager();
504                        if (logger.isDebugEnabled()) {
505                                logger.debug("Closing JDO PersistenceManager [" + pm + "] after transaction");
506                        }
507                        PersistenceManagerFactoryUtils.releasePersistenceManager(pm, getPersistenceManagerFactory());
508                }
509                else {
510                        logger.debug("Not closing pre-bound JDO PersistenceManager after transaction");
511                }
512        }
513
514        /**
515         * Convert the given JDOException to an appropriate exception from the
516         * {@code org.springframework.dao} hierarchy.
517         * <p>The default implementation delegates to the JdoDialect.
518         * May be overridden in subclasses.
519         * @param ex JDOException that occured
520         * @return the corresponding DataAccessException instance
521         * @see JdoDialect#translateException
522         */
523        protected DataAccessException convertJdoAccessException(JDOException ex) {
524                return getJdoDialect().translateException(ex);
525        }
526
527
528        /**
529         * JDO transaction object, representing a PersistenceManagerHolder.
530         * Used as transaction object by JdoTransactionManager.
531         */
532        private class JdoTransactionObject extends JdbcTransactionObjectSupport {
533
534                private PersistenceManagerHolder persistenceManagerHolder;
535
536                private boolean newPersistenceManagerHolder;
537
538                private Object transactionData;
539
540                public void setPersistenceManagerHolder(
541                                PersistenceManagerHolder persistenceManagerHolder, boolean newPersistenceManagerHolder) {
542                        this.persistenceManagerHolder = persistenceManagerHolder;
543                        this.newPersistenceManagerHolder = newPersistenceManagerHolder;
544                }
545
546                public PersistenceManagerHolder getPersistenceManagerHolder() {
547                        return this.persistenceManagerHolder;
548                }
549
550                public boolean isNewPersistenceManagerHolder() {
551                        return this.newPersistenceManagerHolder;
552                }
553
554                public boolean hasTransaction() {
555                        return (this.persistenceManagerHolder != null && this.persistenceManagerHolder.isTransactionActive());
556                }
557
558                public void setTransactionData(Object transactionData) {
559                        this.transactionData = transactionData;
560                        this.persistenceManagerHolder.setTransactionActive(true);
561                }
562
563                public Object getTransactionData() {
564                        return this.transactionData;
565                }
566
567                public void setRollbackOnly() {
568                        Transaction tx = this.persistenceManagerHolder.getPersistenceManager().currentTransaction();
569                        if (tx.isActive()) {
570                                tx.setRollbackOnly();
571                        }
572                        if (hasConnectionHolder()) {
573                                getConnectionHolder().setRollbackOnly();
574                        }
575                }
576
577                @Override
578                public boolean isRollbackOnly() {
579                        Transaction tx = this.persistenceManagerHolder.getPersistenceManager().currentTransaction();
580                        return tx.getRollbackOnly();
581                }
582
583                @Override
584                public void flush() {
585                        try {
586                                this.persistenceManagerHolder.getPersistenceManager().flush();
587                        }
588                        catch (JDOException ex) {
589                                throw convertJdoAccessException(ex);
590                        }
591                }
592        }
593
594
595        /**
596         * Holder for suspended resources.
597         * Used internally by {@code doSuspend} and {@code doResume}.
598         */
599        private static class SuspendedResourcesHolder {
600
601                private final PersistenceManagerHolder persistenceManagerHolder;
602
603                private final ConnectionHolder connectionHolder;
604
605                private SuspendedResourcesHolder(PersistenceManagerHolder pmHolder, ConnectionHolder conHolder) {
606                        this.persistenceManagerHolder = pmHolder;
607                        this.connectionHolder = conHolder;
608                }
609
610                private PersistenceManagerHolder getPersistenceManagerHolder() {
611                        return this.persistenceManagerHolder;
612                }
613
614                private ConnectionHolder getConnectionHolder() {
615                        return this.connectionHolder;
616                }
617        }
618
619}