/*
* Copyright 2011 Red Hat, Inc. and/or its affiliates.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
package org.infinispan.configuration.cache;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.TransactionMode;
import org.infinispan.transaction.TransactionProtocol;
import org.infinispan.transaction.lookup.GenericTransactionManagerLookup;
import org.infinispan.transaction.lookup.TransactionManagerLookup;
import org.infinispan.transaction.lookup.TransactionSynchronizationRegistryLookup;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import javax.transaction.Synchronization;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;
import java.util.concurrent.TimeUnit;
/**
* Defines transactional (JTA) characteristics of the cache.
*
* @author pmuir
* @author Pedro Ruivo
*/
public class TransactionConfigurationBuilder extends AbstractConfigurationChildBuilder<TransactionConfiguration> {
private static Log log = LogFactory.getLog(TransactionConfigurationBuilder.class);
private boolean autoCommit = true;
private long cacheStopTimeout = TimeUnit.SECONDS.toMillis(30);
private boolean eagerLockingSingleNode = false;
LockingMode lockingMode = LockingMode.OPTIMISTIC;
private boolean syncCommitPhase = true;
private boolean syncRollbackPhase = false;
private TransactionManagerLookup transactionManagerLookup;
private TransactionSynchronizationRegistryLookup transactionSynchronizationRegistryLookup;
TransactionMode transactionMode = null;
private boolean useEagerLocking = false;
private boolean useSynchronization = false;
private final RecoveryConfigurationBuilder recovery;
private boolean use1PcForAutoCommitTransactions = false;
private TransactionProtocol transactionProtocol = TransactionProtocol.TWO_PHASE_COMMIT;
TransactionConfigurationBuilder(ConfigurationBuilder builder) {
super(builder);
this.recovery = new RecoveryConfigurationBuilder(this);
}
/**
* If the cache is transactional (i.e. {@link #transactionMode(org.infinispan.transaction.TransactionMode)} == TransactionMode.TRANSACTIONAL)
* and transactionAutoCommit is enabled then for single operation transactions
* the user doesn't need to manually start a transaction, but a transactions
* is injected by the system. Defaults to true.
*/
public TransactionConfigurationBuilder autoCommit(boolean b) {
this.autoCommit = b;
return this;
}
/**
* If there are any ongoing transactions when a cache is stopped, Infinispan waits for ongoing
* remote and local transactions to finish. The amount of time to wait for is defined by the
* cache stop timeout. It is recommended that this value does not exceed the transaction timeout
* because even if a new transaction was started just before the cache was stopped, this could
* only last as long as the transaction timeout allows it.
* <p/>
* This configuration property may be adjusted at runtime
*
* @deprecated use {@link #cacheStopTimeout(long)} instead
*/
@Deprecated
public TransactionConfigurationBuilder cacheStopTimeout(int i) {
this.cacheStopTimeout = i;
return this;
}
/**
* If there are any ongoing transactions when a cache is stopped, Infinispan waits for ongoing
* remote and local transactions to finish. The amount of time to wait for is defined by the
* cache stop timeout. It is recommended that this value does not exceed the transaction timeout
* because even if a new transaction was started just before the cache was stopped, this could
* only last as long as the transaction timeout allows it.
* <p/>
* This configuration property may be adjusted at runtime
*/
public TransactionConfigurationBuilder cacheStopTimeout(long l) {
this.cacheStopTimeout = l;
return this;
}
/**
* Only has effect for DIST mode and when useEagerLocking is set to true. When this is enabled,
* then only one node is locked in the cluster, disregarding numOwners config. On the opposite,
* if this is false, then on all cache.lock() calls numOwners RPCs are being performed. The node
* that gets locked is the main data owner, i.e. the node where data would reside if
* numOwners==1. If the node where the lock resides crashes, then the transaction is marked for
* rollback - data is in a consistent state, no fault tolerance.
*
* @deprecated starting with Infinispan 5.1 single node locking is used by default
*/
@Deprecated
public TransactionConfigurationBuilder eagerLockingSingleNode(boolean b) {
this.eagerLockingSingleNode = b;
return this;
}
/**
* Configures whether the cache uses optimistic or pessimistic locking. If the cache is not
* transactional then the locking mode is ignored.
*
* @see org.infinispan.config.Configuration#isTransactionalCache()
*/
public TransactionConfigurationBuilder lockingMode(LockingMode lockingMode) {
this.lockingMode = lockingMode;
return this;
}
/**
* If true, the cluster-wide commit phase in two-phase commit (2PC) transactions will be
* synchronous, so Infinispan will wait for responses from all nodes to which the commit was
* sent. Otherwise, the commit phase will be asynchronous. Keeping it as false improves
* performance of 2PC transactions, since any remote failures are trapped during the prepare
* phase anyway and appropriate rollbacks are issued.
* <p/>
* This configuration property may be adjusted at runtime
*/
public TransactionConfigurationBuilder syncCommitPhase(boolean b) {
this.syncCommitPhase = b;
return this;
}
/**
* If true, the cluster-wide rollback phase in two-phase commit (2PC) transactions will be
* synchronous, so Infinispan will wait for responses from all nodes to which the rollback was
* sent. Otherwise, the rollback phase will be asynchronous. Keeping it as false improves
* performance of 2PC transactions.
* <p />
*
* This configuration property may be adjusted at runtime.
*
* @param b
* @return
*/
public TransactionConfigurationBuilder syncRollbackPhase(boolean b) {
this.syncRollbackPhase = b;
return this;
}
/**
* Configure Transaction manager lookup directly using an instance of TransactionManagerLookup.
* Calling this method marks the cache as transactional.
*/
public TransactionConfigurationBuilder transactionManagerLookup(TransactionManagerLookup tml) {
this.transactionManagerLookup = tml;
return this;
}
/**
* Configure Transaction Synchronization Registry lookup directly using an instance of
* TransactionManagerLookup. Calling this method marks the cache as transactional.
*/
public TransactionConfigurationBuilder transactionSynchronizationRegistryLookup(
TransactionSynchronizationRegistryLookup lookup) {
this.transactionSynchronizationRegistryLookup = lookup;
return this;
}
public TransactionConfigurationBuilder transactionMode(TransactionMode transactionMode) {
this.transactionMode = transactionMode;
return this;
}
/**
* Only has effect for DIST mode and when useEagerLocking is set to true. When this is enabled,
* then only one node is locked in the cluster, disregarding numOwners config. On the opposite,
* if this is false, then on all cache.lock() calls numOwners RPCs are being performed. The node
* that gets locked is the main data owner, i.e. the node where data would reside if
* numOwners==1. If the node where the lock resides crashes, then the transaction is marked for
* rollback - data is in a consistent state, no fault tolerance.
* <p />
* Note: Starting with infinispan 5.1 eager locking is replaced with pessimistic locking and can
* be enforced by setting transaction's locking mode to PESSIMISTIC.
*
* @param b
* @return
*/
@Deprecated
public TransactionConfigurationBuilder useEagerLocking(boolean b) {
this.useEagerLocking = b;
return this;
}
/**
* Configures whether the cache registers a synchronization with the transaction manager, or registers itself as an
* XA resource. It is often unnecessary to register as a full XA resource unless you intend to make use of recovery
* as well, and registering a synchronization is significantly more efficient.
* @param b if true, {@link Synchronization}s are used rather than {@link XAResource}s when communicating with a {@link TransactionManager}.
*/
public TransactionConfigurationBuilder useSynchronization(boolean b) {
this.useSynchronization = b;
return this;
}
/**
* This method allows configuration of the transaction recovery cache. When this method is
* called, it automatically enables recovery. So, if you want it to be disabled, make sure you
* call {@link org.infinispan.config.FluentConfiguration.RecoveryConfig#disable()}
*/
public RecoveryConfigurationBuilder recovery() {
recovery.enable();
return recovery;
}
/**
* Before Infinispan 5.1 you could access the cache both transactionally and
* non-transactionally. Naturally the non-transactional access is faster and
* offers less consistency guarantees. From Infinispan 5.1 onwards, mixed
* access is no longer supported, so if you wanna speed up transactional
* caches and you're ready to trade some consistency guarantees, you can
* enable use1PcForAutoCommitTransactions. <p/>
*
* What this configuration option does is force an induced transaction,
* that has been started by Infinispan as a result of enabling autoCommit,
* to commit in a single phase. So only 1 RPC instead of 2RPCs as in the
* case of a full 2 Phase Commit (2PC).
*/
public TransactionConfigurationBuilder use1PcForAutoCommitTransactions(boolean b) {
this.use1PcForAutoCommitTransactions = b;
return this;
}
@Override
void validate() {
if (transactionManagerLookup == null) {
if (!getBuilder().invocationBatching().enabled) {
transactionManagerLookup = new GenericTransactionManagerLookup();
} else {
if (!useSynchronization) log.debug("Switching to Synchronization based enlistment.");
useSynchronization = true;
}
}
if(transactionProtocol.isTotalOrder()) {
validateTotalOrder();
return;
}
if (transactionProtocol.isPassiveReplication()) {
validatePassiveReplication();
}
}
private void validateTotalOrder() {
//total order only supports transactional caches
if(transactionMode == TransactionMode.NON_TRANSACTIONAL) {
log.tracef("Non transactional cache is not supported in Total Order protocol. Total Order protocol will" +
" be ignored.");
return ;
}
//total order only supports replicated and distributed mode
if(!clustering().cacheMode().isReplicated() && !clustering().cacheMode().isDistributed()) {
log.cacheModeNotSupportedByTOProtocol(clustering().cacheMode().friendlyCacheModeString());
transactionProtocol = TransactionProtocol.TWO_PHASE_COMMIT;
return;
}
//eager locking is no longer needed
if(useEagerLocking) {
log.tracef("Eager locking will be ignore with Total Order protocol");
}
}
private void validatePassiveReplication() {
//passive replication only supports transactional caches
if(transactionMode == TransactionMode.NON_TRANSACTIONAL) {
log.tracef("Non transactional cache is not supported in Passive Replication protocol. Passive Replication " +
"protocol will be ignored.");
return ;
}
if(!clustering().cacheMode().isReplicated()) {
log.cacheModeNotSupportedByPRProtocol(clustering().cacheMode().friendlyCacheModeString());
transactionProtocol = TransactionProtocol.TWO_PHASE_COMMIT;
}
}
@Override
TransactionConfiguration create() {
if (useEagerLocking) {
lockingMode = LockingMode.PESSIMISTIC;
}
if (transactionMode == null && getBuilder().invocationBatching().enabled)
transactionMode = TransactionMode.TRANSACTIONAL;
else if (transactionMode == null)
transactionMode = TransactionMode.NON_TRANSACTIONAL;
return new TransactionConfiguration(autoCommit, cacheStopTimeout, eagerLockingSingleNode, lockingMode, syncCommitPhase,
syncRollbackPhase, transactionManagerLookup, transactionSynchronizationRegistryLookup, transactionMode,
useEagerLocking, useSynchronization, use1PcForAutoCommitTransactions, recovery.create(), transactionProtocol);
}
@Override
public TransactionConfigurationBuilder read(TransactionConfiguration template) {
this.autoCommit = template.autoCommit();
this.cacheStopTimeout = template.cacheStopTimeout();
this.eagerLockingSingleNode = template.eagerLockingSingleNode();
this.lockingMode = template.lockingMode();
this.syncCommitPhase = template.syncCommitPhase();
this.syncRollbackPhase = template.syncRollbackPhase();
this.transactionManagerLookup = template.transactionManagerLookup();
this.transactionMode = template.transactionMode();
this.transactionSynchronizationRegistryLookup = template.transactionSynchronizationRegistryLookup();
this.useEagerLocking = template.useEagerLocking();
this.useSynchronization = template.useSynchronization();
this.use1PcForAutoCommitTransactions = template.use1PcForAutoCommitTransactions();
this.recovery.read(template.recovery());
this.transactionProtocol = template.transactionProtocol();
return this;
}
@Override
public String toString() {
return "TransactionConfigurationBuilder{" +
"autoCommit=" + autoCommit +
", cacheStopTimeout=" + cacheStopTimeout +
", eagerLockingSingleNode=" + eagerLockingSingleNode +
", lockingMode=" + lockingMode +
", syncCommitPhase=" + syncCommitPhase +
", syncRollbackPhase=" + syncRollbackPhase +
", transactionManagerLookup=" + transactionManagerLookup +
", transactionSynchronizationRegistryLookup=" + transactionSynchronizationRegistryLookup +
", transactionMode=" + transactionMode +
", useEagerLocking=" + useEagerLocking +
", useSynchronization=" + useSynchronization +
", recovery=" + recovery +
", use1PcForAutoCommitTransactions=" + use1PcForAutoCommitTransactions +
'}';
}
public TransactionConfigurationBuilder transactionProtocol(TransactionProtocol transactionProtocol) {
this.transactionProtocol = transactionProtocol;
return this;
}
}