/* * JBoss, Home of Professional Open Source. * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.cache; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.cache.annotations.Compat; import org.jboss.cache.commands.VisitableCommand; import org.jboss.cache.config.Option; import org.jboss.cache.marshall.MethodCall; import org.jboss.cache.transaction.GlobalTransaction; import org.jboss.cache.transaction.TransactionContext; import org.jboss.cache.transaction.TransactionTable; import org.jboss.cache.util.Immutables; import javax.transaction.Transaction; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A context that holds information regarding the scope of a single invocation. May delegate some calls to a {@link org.jboss.cache.transaction.TransactionContext} * if one is in scope. *

* Note that prior to 3.0.0, InvocationContext was a concrete class and not an abstract one. *

* * @author Manik Surtani (manik AT jboss DOT org) * @see org.jboss.cache.transaction.TransactionContext */ @SuppressWarnings("deprecation") @Compat(notes = "This really ought to be an interface, just like TransactionContext, but since this is public API making this an interface will break binary compat with 2.x.") public abstract class InvocationContext { private static final Log log = LogFactory.getLog(InvocationContext.class); private static final boolean trace = log.isTraceEnabled(); private Transaction transaction; private GlobalTransaction globalTransaction; protected TransactionContext transactionContext; private Option optionOverrides; // defaults to true. private boolean originLocal = true; private boolean localRollbackOnly; private boolean bypassUnmarshalling = false; @Deprecated private MethodCall methodCall; @Deprecated private VisitableCommand command; /** * Set of Fqns loaded by the cache loader interceptor. Only recorded if needed, such as by the ActivationInterceptor */ private Set fqnsLoaded; /** * LinkedHashSet of locks acquired by the invocation. We use a LinkedHashSet because we need efficient Set semantics * but also need guaranteed ordering for use by lock release code (see JBCCACHE-874). *

* This needs to be unchecked since we support both MVCC (Fqns held here) or legacy Opt/Pess locking (NodeLocks held here). * once we drop support for opt/pess locks we can genericise this to contain Fqns. - Manik Surtani, June 2008 */ protected LinkedHashSet invocationLocks; /** * Retrieves a node from the registry of looked up nodes in the current scope. * * @param fqn fqn to look up * @return a node, or null if it cannot be found. * @since 3.0. */ public abstract NodeSPI lookUpNode(Fqn fqn); /** * Puts an entry in the registry of looked up nodes in the current scope. * * @param f fqn to add * @param n node to add * @since 3.0. */ public abstract void putLookedUpNode(Fqn f, NodeSPI n); /** * Adds a map of looked up nodes to the current map of looked up nodes * * @param lookedUpNodes looked up nodes to add */ public abstract void putLookedUpNodes(Map lookedUpNodes); /** * Clears the registry of looked up nodes. * * @since 3.0. */ public abstract void clearLookedUpNodes(); /** * Retrieves a map of nodes looked up within the current invocation's scope. * * @return a map of looked up nodes. * @since 3.0 */ public abstract Map getLookedUpNodes(); /** * Marks teh context as only rolling back. * * @param localRollbackOnly if true, the context is only rolling back. */ public void setLocalRollbackOnly(boolean localRollbackOnly) { this.localRollbackOnly = localRollbackOnly; } /** * Retrieves the transaction associated with this invocation * * @return The transaction associated with this invocation */ public Transaction getTransaction() { return transaction; } /** * Sets a transaction object on the invocation context. * * @param transaction transaction to set */ public void setTransaction(Transaction transaction) { this.transaction = transaction; } /** * @return the transaction entry associated with the current transaction, or null if the current thread is not associated with a transaction. * @since 2.2.0 */ public TransactionContext getTransactionContext() { return transactionContext; } /** * Sets the transaction context to be associated with the current thread. * * @param transactionContext transaction context to set * @since 2.2.0 */ public void setTransactionContext(TransactionContext transactionContext) { this.transactionContext = transactionContext; } /** * Retrieves the global transaction associated with this invocation * * @return the global transaction associated with this invocation */ public GlobalTransaction getGlobalTransaction() { return globalTransaction; } /** * Sets the global transaction associated with this invocation * * @param globalTransaction global transaction to set */ public void setGlobalTransaction(GlobalTransaction globalTransaction) { this.globalTransaction = globalTransaction; } /** * Retrieves the option overrides associated with this invocation * * @return the option overrides associated with this invocation */ public Option getOptionOverrides() { if (optionOverrides == null) { optionOverrides = new Option(); } return optionOverrides; } /** * @return true of no options have been set on this context, false otherwise. */ public boolean isOptionsUninitialised() { return optionOverrides == null; } /** * Sets the option overrides to be associated with this invocation * * @param optionOverrides options to set */ public void setOptionOverrides(Option optionOverrides) { this.optionOverrides = optionOverrides; } /** * Tests if this invocation originated locally or from a remote cache. * * @return true if the invocation originated locally. */ public boolean isOriginLocal() { return originLocal; } /** * Returns an immutable, defensive copy of the List of locks currently maintained for the current scope. *

* Note that if a transaction is in scope, implementations should retrieve these locks from the {@link org.jboss.cache.transaction.TransactionContext}. * Retrieving locks from this method should always ensure they are retrieved from the appropriate scope. *

* Note that currently (as of 3.0.0) this list is unchecked. This is to allow support for both MVCC (which uses Fqns as locks) * as well as legacy Optimistic and Pessimistic Locking schemes (which use {@link org.jboss.cache.lock.NodeLock} as locks). Once support for * legacy node locking schemes are dropped, this method will be more strongly typed to return List. * * @return locks held in current scope. */ @SuppressWarnings("unchecked") public List getLocks() { // first check transactional scope if (transactionContext != null) return transactionContext.getLocks(); return invocationLocks == null || invocationLocks.isEmpty() ? Collections.emptyList() : Immutables.immutableListConvert(invocationLocks); } /** * Adds a List of locks to the currently maintained collection of locks acquired. *

* Note that if a transaction is in scope, implementations should record locks on the {@link org.jboss.cache.transaction.TransactionContext}. * Adding locks using this method should always ensure they are applied to the appropriate scope. *

* Note that currently (as of 3.0.0) this list is unchecked. This is to allow support for both MVCC (which uses Fqns as locks) * as well as legacy Optimistic and Pessimistic Locking schemes (which use {@link org.jboss.cache.lock.NodeLock} as locks). Once support for * legacy node locking schemes are dropped, this method will be more strongly typed to accept List. * * @param locks locks to add */ @SuppressWarnings("unchecked") public void addAllLocks(List locks) { // first check transactional scope if (transactionContext != null) { transactionContext.addAllLocks(locks); } else { // no need to worry about concurrency here - a context is only valid for a single thread. if (invocationLocks == null) invocationLocks = new LinkedHashSet(4); invocationLocks.addAll(locks); } } /** * Adds a lock to the currently maintained collection of locks acquired. *

* Note that if a transaction is in scope, implementations should record this lock on the {@link org.jboss.cache.transaction.TransactionContext}. * Using this method should always ensure that the appropriate scope is used. *

* Note that currently (as of 3.0.0) this lock is weakly typed. This is to allow support for both MVCC (which uses {@link Fqn}s as locks) * as well as legacy Optimistic and Pessimistic Locking schemes (which use {@link org.jboss.cache.lock.NodeLock} as locks). Once support for * legacy node locking schemes are dropped, this method will be more strongly typed to accept {@link Fqn}. * * @param lock lock to add */ @SuppressWarnings("unchecked") public void addLock(Object lock) { // first check transactional scope if (transactionContext != null) { transactionContext.addLock(lock); } else { // no need to worry about concurrency here - a context is only valid for a single thread. if (invocationLocks == null) invocationLocks = new LinkedHashSet(4); invocationLocks.add(lock); } } /** * Removes a lock from the currently maintained collection of locks acquired. *

* Note that if a transaction is in scope, implementations should remove this lock from the {@link org.jboss.cache.transaction.TransactionContext}. * Using this method should always ensure that the lock is removed from the appropriate scope. *

* Note that currently (as of 3.0.0) this lock is weakly typed. This is to allow support for both MVCC (which uses {@link Fqn}s as locks) * as well as legacy Optimistic and Pessimistic Locking schemes (which use {@link org.jboss.cache.lock.NodeLock} as locks). Once support for * legacy node locking schemes are dropped, this method will be more strongly typed to accept {@link Fqn}. * * @param lock lock to remove */ @SuppressWarnings("unchecked") public void removeLock(Object lock) { // first check transactional scope if (transactionContext != null) { transactionContext.removeLock(lock); } else { // no need to worry about concurrency here - a context is only valid for a single thread. if (invocationLocks != null) invocationLocks.remove(lock); } } /** * Clears all locks from the currently maintained collection of locks acquired. *

* Note that if a transaction is in scope, implementations should clear locks from the {@link org.jboss.cache.transaction.TransactionContext}. * Using this method should always ensure locks are cleared in the appropriate scope. *

* Note that currently (as of 3.0.0) this lock is weakly typed. This is to allow support for both MVCC (which uses {@link Fqn}s as locks) * as well as legacy Optimistic and Pessimistic Locking schemes (which use {@link org.jboss.cache.lock.NodeLock} as locks). Once support for * legacy node locking schemes are dropped, this method will be more strongly typed to accept {@link Fqn}. */ public void clearLocks() { // first check transactional scope if (transactionContext != null) { transactionContext.clearLocks(); } else { // no need to worry about concurrency here - a context is only valid for a single thread. if (invocationLocks != null) invocationLocks.clear(); } } /** * Note that if a transaction is in scope, implementations should test this lock from on {@link org.jboss.cache.transaction.TransactionContext}. * Using this method should always ensure locks checked in the appropriate scope. * * @param lock lock to test * @return true if the lock being tested is already held in the current scope, false otherwise. */ public boolean hasLock(Object lock) { // first check transactional scope if (transactionContext != null) { return transactionContext.hasLock(lock); } else { return invocationLocks != null && invocationLocks.contains(lock); } } /** * @return true if options exist to suppress locking - false otherwise. Note that this is only used by the {@link org.jboss.cache.interceptors.PessimisticLockInterceptor}. */ public boolean isLockingSuppressed() { return getOptionOverrides() != null && getOptionOverrides().isSuppressLocking(); } /** * If set to true, the invocation is assumed to have originated locally. If set to false, * assumed to have originated from a remote cache. * * @param originLocal flag to set */ public void setOriginLocal(boolean originLocal) { this.originLocal = originLocal; } /** * @return true if the current transaction is set to rollback only. */ public boolean isLocalRollbackOnly() { return localRollbackOnly; } /** * Resets the context, freeing up any references. */ public void reset() { transaction = null; globalTransaction = null; optionOverrides = null; originLocal = true; invocationLocks = null; methodCall = null; command = null; fqnsLoaded = null; bypassUnmarshalling = false; } /** * This is a "copy-factory-method" that should be used whenever a clone of this class is needed. The resulting instance * is equal() to, but not ==, to the InvocationContext invoked on. Note that this is a shallow copy with the exception * of the Option object, which is deep, as well as any collections held on the context such as locks. Note that the reference * to a {@link org.jboss.cache.transaction.TransactionContext}, if any, is maintained. * * @return a new InvocationContext */ @SuppressWarnings("unchecked") public abstract InvocationContext copy(); /** * Sets the state of the InvocationContext based on the template context passed in * * @param template template to copy from */ public void setState(InvocationContext template) { if (template == null) { throw new NullPointerException("Template InvocationContext passed in to InvocationContext.setState() passed in is null"); } this.setGlobalTransaction(template.getGlobalTransaction()); this.setLocalRollbackOnly(template.isLocalRollbackOnly()); this.setOptionOverrides(template.getOptionOverrides()); this.setOriginLocal(template.isOriginLocal()); this.setTransaction(template.getTransaction()); } /** * @return the method call associated with this invocation */ @Deprecated @SuppressWarnings("deprecation") public MethodCall getMethodCall() { if (methodCall == null) { methodCall = createMethodCall(); } return methodCall; } @SuppressWarnings("deprecation") private MethodCall createMethodCall() { if (command == null) return null; MethodCall call = new MethodCall(); call.setMethodId(command.getCommandId()); call.setArgs(command.getParameters()); return call; } /** * Sets the method call associated with this invocation. * * @param methodCall methodcall to set * @deprecated not used anymore. Interceptors will get a {@link org.jboss.cache.commands.ReplicableCommand} instance passed in along with an InvocationContext. */ @Deprecated public void setMethodCall(MethodCall methodCall) { this.methodCall = methodCall; } /** * If the lock acquisition timeout is overridden for current call using an option, then return that one. * If not overridden, return default value. * * @param timeout timeout to fall back to * @return timeout to use */ public long getLockAcquisitionTimeout(long timeout) { // TODO: this stuff really doesn't belong here. Put it somewhere else. if (getOptionOverrides() != null && getOptionOverrides().getLockAcquisitionTimeout() >= 0) { timeout = getOptionOverrides().getLockAcquisitionTimeout(); } return timeout; } /** * This is only used for backward compatibility with old interceptors implementation and should NOT be * use by any new custom interceptors. The commands is now passed in as the second param in each implementing * handlers (handler = method in ChainedInterceptor class) * * @param cacheCommand command to set */ @Deprecated @SuppressWarnings("deprecation") public void setCommand(VisitableCommand cacheCommand) { this.command = cacheCommand; } /** * @return command that is in scope * @see #setCommand(org.jboss.cache.commands.VisitableCommand) */ @Deprecated @SuppressWarnings("deprecation") public VisitableCommand getCommand() { return command; } /** * @return true if there is current transaction associated with the invocation, and this transaction is in a valid state. */ public boolean isValidTransaction() { // ought to move to the transaction context return transaction != null && TransactionTable.isValid(transaction); } /** * Throws the given throwable provided no options suppress or prevent this from happening. * * @param e throwable to throw * @throws Throwable if allowed to throw one. */ public void throwIfNeeded(Throwable e) throws Throwable { // TODO: this stuff really doesn't belong here. Put it somewhere else. Option optionOverride = getOptionOverrides(); boolean shouldRethtrow = optionOverride == null || !optionOverride.isFailSilently(); if (!shouldRethtrow) { if (trace) { log.trace("There was a problem handling this request, but failSilently was set, so suppressing exception", e); } return; } throw e; } @SuppressWarnings("unchecked") protected void doCopy(InvocationContext copy) { copy.command = command; copy.globalTransaction = globalTransaction; copy.invocationLocks = invocationLocks == null ? null : new LinkedHashSet(invocationLocks); copy.localRollbackOnly = localRollbackOnly; copy.optionOverrides = optionOverrides == null ? null : optionOverrides.copy(); copy.originLocal = originLocal; copy.transaction = transaction; copy.transactionContext = transactionContext; copy.fqnsLoaded = fqnsLoaded; copy.bypassUnmarshalling = bypassUnmarshalling; } /** * Adds an Fqn to the set of Fqns loaded by the cache loader interceptor. Instantiates the set lazily if needed. * * @param fqn fqn to add */ public void addFqnLoaded(Fqn fqn) { if (fqnsLoaded == null) fqnsLoaded = new HashSet(); fqnsLoaded.add(fqn); } /** * @return Set of Fqns loaded by the cache loader interceptor. Only recorded if needed, such as by the ActivationInterceptor */ public Set getFqnsLoaded() { return fqnsLoaded; } public void setFqnsLoaded(Set fqnsLoaded) { this.fqnsLoaded = fqnsLoaded; } @Override public String toString() { return "InvocationContext{" + "transaction=" + transaction + ", globalTransaction=" + globalTransaction + ", transactionContext=" + transactionContext + ", optionOverrides=" + optionOverrides + ", originLocal=" + originLocal + ", bypassUnmarshalling=" + bypassUnmarshalling + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final InvocationContext that = (InvocationContext) o; if (localRollbackOnly != that.localRollbackOnly) return false; if (bypassUnmarshalling != that.bypassUnmarshalling) return false; if (originLocal != that.originLocal) return false; if (globalTransaction != null ? !globalTransaction.equals(that.globalTransaction) : that.globalTransaction != null) { return false; } if (optionOverrides != null ? !optionOverrides.equals(that.optionOverrides) : that.optionOverrides != null) { return false; } if (transaction != null ? !transaction.equals(that.transaction) : that.transaction != null) return false; return true; } @Override public int hashCode() { int result; result = (transaction != null ? transaction.hashCode() : 0); result = 29 * result + (globalTransaction != null ? globalTransaction.hashCode() : 0); result = 29 * result + (optionOverrides != null ? optionOverrides.hashCode() : 0); result = 29 * result + (originLocal ? 1 : 0); result = 29 * result + (localRollbackOnly ? 1 : 0); result = 29 * result + (bypassUnmarshalling ? 1 : 0); return result; } public void setBypassUnmarshalling(boolean b) { this.bypassUnmarshalling = b; } public boolean isBypassUnmarshalling() { return bypassUnmarshalling; } }