/* * JBoss, Home of Professional Open Source * Copyright 2009, JBoss Inc., and others contributors as indicated * by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * 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, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2009, * @author mark.little@jboss.com */ package org.jboss.stm.internal.reflect; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import com.arjuna.ats.txoj.logging.txojLogger; import org.jboss.stm.LockException; import org.jboss.stm.TransactionException; import org.jboss.stm.annotations.LockFree; import org.jboss.stm.annotations.NestedTopLevel; import org.jboss.stm.annotations.Optimistic; import org.jboss.stm.annotations.Retry; import org.jboss.stm.annotations.Timeout; import org.jboss.stm.annotations.TransactionFree; import org.jboss.stm.annotations.Nested; import org.jboss.stm.annotations.ReadLock; import org.jboss.stm.annotations.WriteLock; import org.jboss.stm.internal.RecoverableContainer; import org.jboss.stm.internal.optimistic.OptimisticLock; import org.jboss.stm.internal.proxy.LockManagerProxy; import org.jboss.stm.internal.proxy.OptimisticLockManagerProxy; import com.arjuna.ats.arjuna.AtomicAction; import com.arjuna.ats.arjuna.ObjectModel; import com.arjuna.ats.arjuna.ObjectType; import com.arjuna.ats.arjuna.TopLevelAction; import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.coordinator.ActionStatus; import com.arjuna.ats.arjuna.coordinator.BasicAction; import com.arjuna.ats.arjuna.objectstore.StoreManager; import com.arjuna.ats.internal.arjuna.objectstore.TwoPhaseVolatileStore; import com.arjuna.ats.txoj.Lock; import com.arjuna.ats.txoj.LockManager; import com.arjuna.ats.txoj.LockMode; import com.arjuna.ats.txoj.LockResult; public class InvocationHandler<T> implements java.lang.reflect.InvocationHandler { /* * If no locks are defined in annotations then we try to use the * names of the methods to infer a lock type to use. * * todo do we need a "disable inference" annotation/rule? Maybe not since you * can always either explicitly define the lock or change the method name! */ @SuppressWarnings("unused") private static final String GETTER_NAME = "GET"; @SuppressWarnings("unused") private static final String SETTER_NAME = "SET"; class LockInformation { public LockInformation (int lockType) { this(lockType, LockManager.defaultSleepTime, LockManager.defaultRetry); } public LockInformation (int lockType, int timeout, int retry) { _lockType = lockType; _timeout = timeout; _retry = retry; } public String toString () { return "Lock < "+LockMode.stringForm(_lockType)+", "+_timeout+", "+_retry+" >"; } public int _lockType; public int _timeout; public int _retry; } /* * Not all possible LockManager options are available. We only support those that we need * at any given moment in STM. */ public InvocationHandler (RecoverableContainer<T> c, T obj) { this(c, obj, null); } public InvocationHandler (RecoverableContainer<T> cont, T obj, Uid u) { _container = cont; _theObject = obj; /* * Do we need to use the optimistic LockManager instance? */ Class<?> c = obj.getClass(); while (c != null) { /* * Default is pessimistic. */ if (c.getAnnotation(Optimistic.class) != null) { _optimistic = true; break; } c = c.getSuperclass(); } if (!_optimistic) { Class<?>[] interfaces = obj.getClass().getInterfaces(); for (Class<?> i : interfaces) { if (i.getAnnotation(Optimistic.class) != null) { _optimistic = true; break; } } } if (_optimistic) { if (!initialiseStore()) { _txObject = null; throw new RuntimeException("Could not initialise ObjectStore!"); } } if (u != null) { if (_optimistic) { _txObject = new OptimisticLockManagerProxy<T>(obj, u, ObjectModel.MULTIPLE, cont); } else _txObject = new LockManagerProxy<T>(obj, u, cont); } else { /* * todo currently optimistic always uses persistent objects, but with an in-memory object store. This * is to ensure that concurrent threads do not interfere with each others current state view. If the application * is written in such a way that a single instance can be used and still be thread-safe, then we could go * back to using RECOVERABLE. Make it an annotation option? */ if (_optimistic) { /* * At this stage we ignore the type and model settings in the container. So annotations override * the default container settings. Consider re-visiting the model such that either this is explicit * for all objects where model/type is different to container, or change instance/container relationship * so the container values continue to match the instance (even if lazily so container data is updated). * Problem with latter approach is that we can have many different types of object coming from the same * container. */ _txObject = new OptimisticLockManagerProxy<T>(obj, ObjectType.ANDPERSISTENT, ObjectModel.MULTIPLE, cont); // recoverable or persistent } else _txObject = new LockManagerProxy<T>(obj, cont); // recoverable or persistent } _methods = obj.getClass().getDeclaredMethods(); /* * Do we need to create (sub-) transactions when each method * is called? */ c = obj.getClass(); // yeah ok, should use isAnnotationPresent ... while (c != null) { if (c.getAnnotation(Nested.class) != null) { _nestedTransactions = true; break; } if (c.getAnnotation(NestedTopLevel.class) != null) { _nestedTopLevel = true; break; } c = c.getSuperclass(); } if (!_nestedTransactions || !_nestedTopLevel) { Class<?>[] interfaces = obj.getClass().getInterfaces(); for (Class<?> i : interfaces) { if (i.getAnnotation(Nested.class) != null) { _nestedTransactions = true; break; } if (i.getAnnotation(NestedTopLevel.class) != null) { _nestedTopLevel = true; break; } } } } public Uid get_uid () { if (_txObject == null) throw new RuntimeException("Transactional object is null!"); return _txObject.get_uid(); } public Object invoke (Object proxy, java.lang.reflect.Method method, Object[] args) throws Throwable { /* * Do nothing currently if not inside of a transaction and * not asked to create transactions for this type of object. * * Automatically create transactions in methods for nested * transaction capability, i.e., duplicate what normal Arjuna * programmers take for granted. */ if (_txObject == null) throw new LockException("Transactional object is null!"); AtomicAction currentTx = null; synchronized (_txObject) { synchronized (_theObject) { AtomicAction act = null; /* * We could maybe be a bit more intelligent here and not create any * nested transaction if TransactionFree is set, but there's very little * overhead in creating the transaction and not using it. */ if (_nestedTransactions) { act = new AtomicAction(); act.begin(); } else { if (_nestedTopLevel) { act = new TopLevelAction(); act.begin(); } } try { LockInformation cachedLock = _cachedMethods.get(method); // todo allow null transaction context - not an issue for now with STM though! if (BasicAction.Current() != null) { Method theMethod = null; /* * Look for the corresponding method in the original object and * check the annotations applied there. * * Check to see if we've cached this before. */ int lockType = -1; boolean lockFree = false; boolean transactionFree = false; if (cachedLock == null) { for (Method mt : _methods) { if (mt.getName().equals(method.getName())) { if (mt.getReturnType().equals(method.getReturnType())) { if (Arrays.equals(mt.getParameterTypes(), method.getParameterTypes())) theMethod = mt; } } } /* * Should we catch common methods, like equals, and call Object... automatically? */ if (theMethod == null) throw new LockException("Could not locate method "+method); /* * What about other lock types? */ if (theMethod.isAnnotationPresent(ReadLock.class)) lockType = LockMode.READ; else { if (theMethod.isAnnotationPresent(WriteLock.class)) lockType = LockMode.WRITE; else { if (theMethod.isAnnotationPresent(TransactionFree.class)) transactionFree = true; else { if (theMethod.isAnnotationPresent(LockFree.class)) lockFree = true; } } } // if TransactionFree then suspend any transactions and don't do locking if (!lockFree && !transactionFree) { int timeout = LockManager.defaultSleepTime; int retry = LockManager.defaultRetry; if (theMethod.isAnnotationPresent(Timeout.class)) timeout = theMethod.getAnnotation(Timeout.class).period(); if (theMethod.isAnnotationPresent(Retry.class)) retry = theMethod.getAnnotation(Retry.class).count(); if (lockType == -1) // default to WRITE lockType = LockMode.WRITE; cachedLock = new LockInformation(lockType, timeout, retry); _cachedMethods.put(method, cachedLock); } else { if (transactionFree) currentTx = AtomicAction.suspend(); } } // TODO type specific concurrency control (define Lock class in annotation?) if (!lockFree && !transactionFree) { int result = _txObject.setlock((_optimistic ? new OptimisticLock(cachedLock._lockType) : new Lock(cachedLock._lockType)), cachedLock._retry, cachedLock._timeout); if (result != LockResult.GRANTED) { throw new LockException(Thread.currentThread()+" could not set "+LockMode.stringForm(cachedLock._lockType)+" lock. Got: "+LockResult.stringForm(result)); } } } try { return method.invoke(_theObject, args); } catch (InvocationTargetException e) { if (txojLogger.logger.isTraceEnabled()) { Throwable ae = e.getCause() != null ? e.getCause() : e; txojLogger.logger.tracef("STM InvocationHandler::invoke application method %s threw exception %s", method.getName(), ae.getMessage()); } throw e.getCause() != null ? e.getCause() : e; } } finally { if (act != null) { int status = act.commit(); if ((status != ActionStatus.COMMITTED) && (status != ActionStatus.COMMITTING)) { if (currentTx != null) AtomicAction.resume(currentTx); throw new TransactionException("Failed to commit container transaction!", status); } } if (currentTx != null) AtomicAction.resume(currentTx); } } } } /** * It might be useful to get the Container for the object at some points. * * @return the container reference. */ protected final RecoverableContainer<T> getContainer () { return _container; } private static boolean initialiseStore () { synchronized (InvocationHandler.class) { if (_storeManager == null) { try { _storeManager = new StoreManager(null, new TwoPhaseVolatileStore(new ObjectStoreEnvironmentBean()), null); } catch (final IllegalStateException ex) { _storeManager = null; // means store already initialised so check to see if compatible if (StoreManager.setupStore(null, ObjectModel.SINGLE).getClass().equals(TwoPhaseVolatileStore.class)) { // do nothing, as we are ok } else { ex.printStackTrace(); return false; } } catch (final Throwable ex) { System.err.println("InvocationHandler could not initialise object store for optimistic concurrency control."); ex.printStackTrace(); return false; } } } return true; } private RecoverableContainer<T> _container; // could be a persistent container, but not an issue for this class private T _theObject; private LockManager _txObject; private Method[] _methods; private HashMap<Method, InvocationHandler<T>.LockInformation> _cachedMethods = new HashMap<Method, InvocationHandler<T>.LockInformation>(); private boolean _nestedTransactions = false; // todo change default? private boolean _nestedTopLevel = false; private boolean _optimistic = false; private static StoreManager _storeManager = null; }