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