All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jodd.jtx.JtxTransaction Maven / Gradle / Ivy

There is a newer version: 5.1.0-20190624
Show newest version
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.jtx;

import jodd.log.Logger;
import jodd.log.LoggerFactory;

import static jodd.jtx.JtxStatus.*;
import static jodd.jtx.JtxTransactionMode.DEFAULT_TIMEOUT;

import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;

/**
 * Transaction is an unit of work that is performed by one or more resources.
 * Created and controlled by {@link JtxTransactionManager transaction manager}.
 * 

* Transaction is associated to a thread from where it was created. * Transaction may have more than one resource attached. *

* Committing and rolling back transaction actually performs commit/rollback on * all attached resources. Therefore, it may happens that committing of one resource * fails, what actually breaks the atomicity of the transaction. If transaction is * attached just to one resource, atomicity is saved. *

* Only one resource of some type may exists in the transaction. There is no way to have * two resources of the same type in one transaction. *

* This class is responsible for transaction life-cycle. It can be consider that all * work is scoped to associated thread. */ public class JtxTransaction { private static final Logger log = LoggerFactory.getLogger(JtxTransaction.class); // ---------------------------------------------------------------- init protected final JtxTransactionManager txManager; protected final JtxTransactionMode mode; protected final Set resources; protected final Object scope; protected final long deadline; protected final boolean startAsActive; protected Throwable rollbackCause; protected JtxStatus status; /** * Creates new transaction. Should be invoked by {@link jodd.jtx.JtxTransactionManager}. * If transaction is set as active, it will be actually created, meaning * that it is the first transaction on this connection i.e. in this session. * If transaction is not active, transaction object will be created, * but the real transaction not, and it is expected that one is already created before. * * @param txManager jtx manager * @param mode transaction mode * @param scope transaction live scope within the other transaction requests are ignored * @param active if true it is an active transaction, otherwise it's not */ public JtxTransaction(JtxTransactionManager txManager, JtxTransactionMode mode, Object scope, boolean active) { this.txManager = txManager; this.mode = mode; this.scope = scope; this.resources = new HashSet<>(); this.deadline = mode.getTransactionTimeout() == DEFAULT_TIMEOUT ? DEFAULT_TIMEOUT : System.currentTimeMillis() + (mode.getTransactionTimeout() * 1000L); this.status = active ? STATUS_ACTIVE : STATUS_NO_TRANSACTION; this.startAsActive = active; txManager.associateTransaction(this); if (log.isDebugEnabled()) { log.debug("New JTX {status:" + this.status + ", mode:" + this.mode + '}'); } } /** * Returns transaction mode. */ public JtxTransactionMode getTransactionMode() { return mode; } /** * Returns transaction manager that owns this transaction. */ public JtxTransactionManager getTransactionManager() { return txManager; } /** * Returns transaction scope if exist, or null. */ public Object getScope() { return scope; } // ---------------------------------------------------------------- status /** * Returns current transaction status. */ public JtxStatus getStatus() { return status; } /** * Returns true if transaction started as active one. * This value is never changed, while {@link #getStatus() status} * changes during the execution. */ public boolean isStartAsActive() { return startAsActive; } /** * Returns true if transaction is active. * This status changes during the transaction flow. */ public boolean isActive() { return status == STATUS_ACTIVE; } /** * Returns true if transaction is explicitly forbidden, i.e. * session is in auto-commit mode. */ public boolean isNoTransaction() { return status == STATUS_NO_TRANSACTION; } /** * Returns true if transaction and all its resources are committed successfully. */ public boolean isCommitted() { return status == STATUS_COMMITTED; } /** * Returns true if transaction and all its resources are rolled-back successfully. */ public boolean isRolledback() { return status == STATUS_ROLLEDBACK; } /** * Returns true if transaction is either committed or rolled back. */ public boolean isCompleted() { return status == STATUS_COMMITTED || status == STATUS_ROLLEDBACK; } // ---------------------------------------------------------------- rollback /** * Modify the transaction associated with the target object such that the only possible outcome * of the transaction is to roll back the transaction. */ public void setRollbackOnly() { setRollbackOnly(null); } /** * Modify the transaction associated with the target object such that the only possible outcome * of the transaction is to roll back the transaction. */ public void setRollbackOnly(Throwable th) { if (!isNoTransaction()) { if ((status != STATUS_MARKED_ROLLBACK) && (status != STATUS_ACTIVE)) { throw new JtxException("TNo active TX that can be marked as rollback only"); } } rollbackCause = th; status = STATUS_MARKED_ROLLBACK; } /** * Returns true if transaction is marked as rollback only. */ public boolean isRollbackOnly() { return status == STATUS_MARKED_ROLLBACK; } // ---------------------------------------------------------------- timeout /** * Sets the rollback-only if the deadline has been reached and throws an exception. */ protected void checkTimeout() { if (deadline == DEFAULT_TIMEOUT) { return; } if (this.deadline - System.currentTimeMillis() < 0) { setRollbackOnly(); throw new JtxException("TX timed out, marked as rollback only"); } } // ---------------------------------------------------------------- core /** * Commit and completes current transaction. Transaction is committed on all attached resources. After, resources * are detached from the transaction. When this method completes,transaction is no longer * associated with current thread. */ public void commit() { checkTimeout(); commitOrRollback(true); } /** * Roll back and completes current transaction. Transaction is rolled back on all attached resources. * Resource are then detached from the transaction. When this method completes, transaction is no * longer associated with current thread. */ public void rollback() { commitOrRollback(false); } /** * Performs either commit or rollback on all transaction resources. */ protected void commitOrRollback(boolean doCommit) { if (log.isDebugEnabled()) { if (doCommit) { log.debug("Commit JTX"); } else { log.debug("Rollback JTX"); } } boolean forcedRollback = false; if (!isNoTransaction()) { if (isRollbackOnly()) { if (doCommit) { doCommit = false; forcedRollback = true; } } else if (!isActive()) { if (isCompleted()) { throw new JtxException("TX is already completed, commit or rollback should be called once per TX"); } throw new JtxException("No active TX to " + (doCommit ? "commit" : "rollback")); } } if (doCommit) { commitAllResources(); } else { rollbackAllResources(forcedRollback); } } // ---------------------------------------------------------------- resources /** * Commits all attached resources. On successful commit, resource will be closed * and detached from this transaction. On exception, resource remains attached * to transaction. *

* All resources will be committed, even if commit fails on some in that process. * If there was at least one failed commit, its exception will be re-thrown after finishing * committing all resources, and transaction will be marked as rollback only. */ protected void commitAllResources() throws JtxException { status = STATUS_COMMITTING; Exception lastException = null; Iterator it = resources.iterator(); while (it.hasNext()) { JtxResource resource = it.next(); try { resource.commitTransaction(); it.remove(); } catch (Exception ex) { lastException = ex; } } if (lastException != null) { setRollbackOnly(lastException); throw new JtxException("Commit failed: one or more TX resources couldn't commit a TX", lastException); } txManager.removeTransaction(this); status = STATUS_COMMITTED; } /** * Rollbacks all attached resources. Resource will be closed. and detached from this transaction. * If exception occurs, it will be rethrown at the end. */ protected void rollbackAllResources(boolean wasForced) { status = STATUS_ROLLING_BACK; Exception lastException = null; Iterator it = resources.iterator(); while (it.hasNext()) { JtxResource resource = it.next(); try { resource.rollbackTransaction(); } catch (Exception ex) { lastException = ex; } finally { it.remove(); } } txManager.removeTransaction(this); status = STATUS_ROLLEDBACK; if (lastException != null) { status = STATUS_UNKNOWN; throw new JtxException("Rollback failed: one or more TX resources couldn't rollback a TX", lastException); } if (wasForced) { throw new JtxException("TX rolled back because it has been marked as rollback-only", rollbackCause); } } // ---------------------------------------------------------------- resources /** * Requests a resource. If resource is not found, it will be created and new transaction will be started on it. */ public E requestResource(Class resourceType) { if (isCompleted()) { throw new JtxException("TX is already completed, resource are not available after commit or rollback"); } if (isRollbackOnly()) { throw new JtxException("TX is marked as rollback only, resource are not available", rollbackCause); } if (!isNoTransaction() && !isActive()) { throw new JtxException("Resources are not available since TX is not active"); } checkTimeout(); E resource = lookupResource(resourceType); if (resource == null) { int maxResources = txManager.getMaxResourcesPerTransaction(); if ((maxResources != -1) && (resources.size() >= maxResources)) { throw new JtxException("TX already has attached max. number of resources"); } JtxResourceManager resourceManager = txManager.lookupResourceManager(resourceType); resource = resourceManager.beginTransaction(mode, isActive()); resources.add(new JtxResource<>(this, resourceManager, resource)); } return resource; } /** * Lookups for open resource. Returns null if resource not found. * Only open resources can be found. */ protected E lookupResource(Class resourceType) { for (JtxResource jtxResource : resources) { if (jtxResource.isSameTypeAsResource(resourceType)) { //noinspection unchecked return (E) jtxResource.getResource(); } } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy