/* * 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; import java.lang.reflect.Proxy; import java.util.Iterator; import java.util.Set; import java.util.WeakHashMap; import org.jboss.stm.annotations.Optimistic; import org.jboss.stm.annotations.Transactional; import org.jboss.stm.internal.reflect.InvocationHandler; import com.arjuna.ats.arjuna.ObjectModel; import com.arjuna.ats.arjuna.ObjectType; import com.arjuna.ats.arjuna.StateManager; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.txoj.LockManager; /** * Instances of this class represent the transactional memory within which non-persistent user objects * can be placed and managed. * * Objects must implement an interface through which all transactional accesses occur. We don't * mandate what the interface is, since that will depend upon the business logic. The interface, or * the implementing class, must also use the @Transactional annotation. * * @author marklittle */ /* * Could provide a container that works on any classes without annotations. The rules would have to be * more restrictive: * * (i) all operations are assumed to modify the state (therefore write lock). * (ii) all state is saved and restored. * * Or use a setter/getter pattern to provide flexibility around (i). */ public class RecoverableContainer<T> { /** * Create a container without a name. A name will be assigned automatically. */ public RecoverableContainer () { this(ObjectModel.SINGLE); } /** * Create a named container. * * @param name the name (should be unique, but this is not enforced). */ public RecoverableContainer (final String name) { this(name, ObjectModel.SINGLE); } /* * Create a container without a name. A name will be assigned automatically. * * @param objectModel whether the instances are to be shared across address spaces * or classloaders. */ protected RecoverableContainer (int objectModel) { this(new Uid().stringForm()); _objectModel = objectModel; } /* * Create a named container. * * @param name the name (should be unique, but this is not enforced). * @param global whether the instances are to be shared across address spaces * or classloaders. */ protected RecoverableContainer (final String name, int objectModel) { _name = name; _type = ObjectType.RECOVERABLE; _objectModel = objectModel; } /** * Get the name of the container. * * @return the name. */ public final String name () { return _name; } /** * Given an object we create a new transactional instance of it and return that * for future use. All accesses on the returned object will be managed according to * the rules defined in the various annotations. If the original object instance is used * then no transactional manipulation will occur so you need to be careful! * * All handles are uniquely identified using Uid. * * @param member the instance of type T that you want to be made transactional and persistent. * @return a handle into the transactional memory that the application should use to manipulate the object. */ public synchronized T enlist (T member) { return createHandle(member, ObjectType.RECOVERABLE); } /** * Given an identified for an existing object, create another handle. This is particularly * useful when using pessimistic concurrency control and we need one object instance per * thread to ensure that state is safely managed. * * @param member the instance of type T that you want to be made transactional and persistent. * @param id the Uid of the object. * @return a handle into the transactional memory that the application should use to manipulate the object. */ @SuppressWarnings("unchecked") public synchronized T enlist (T member, Uid id) { if (id == null) return null; /* * Everything that is transactional needs to be explicitly marked as such in * the public API, even if the private methods are the ones that do the * real work. */ checkObjectType(member); /* * Is it already registered? If so just return the same instance. */ T proxy = _transactionalProxies.get(member); if (proxy == null) { Class<?> c = member.getClass(); proxy = (T) Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new InvocationHandler<T>(this, member, id)); _transactionalProxies.put(member, proxy); } return proxy; } /** * Return a handle through which the object should be used, rather than the one * passed in. Can specify the type of the object (recoverable, persistent, neither). */ @SuppressWarnings(value={"unchecked"}) protected synchronized T createHandle (T member, int ot) { /* * Everything that is transactional needs to be explicitly marked as such in * the public API, even if the private methods are the ones that do the * real work. */ checkObjectType(member); /* * Is it already registered? If so just return the same instance. */ T proxy = _transactionalProxies.get(member); if (proxy == null) { Class<?> c = member.getClass(); proxy = (T) Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new InvocationHandler<T>(this, member)); _transactionalProxies.put(member, proxy); } return proxy; } /* * Should the following methods all be protected/package scope, i.e., not for applications * to view and/or call? */ /** * Given a Uid, return the proxy for that instance. * * @param reference the unique identifier for the handle. * @return the handle or null if not present. */ @SuppressWarnings("unchecked") public synchronized T getHandle (Uid reference) { if (reference == null) throw new IllegalArgumentException(); Set<T> keys = _transactionalProxies.keySet(); Iterator<T> iter = keys.iterator(); try { while (iter.hasNext()) { T obj = _transactionalProxies.get(iter.next()); InvocationHandler<T> handler = (InvocationHandler<T>) Proxy.getInvocationHandler(obj); if (handler.get_uid().equals(reference)) return obj; } } catch (final Exception ex) { throw new IllegalArgumentException(ex); } return null; } /** * Given a real object, return the Uid if it exists in this container. * * @param member the real object. * @return the Uid for the object. * @exception throws IllegalArgumentException if the real object is not within the container. */ @SuppressWarnings(value={"unchecked"}) public Uid getUidForOriginal (T member) { T proxy = _transactionalProxies.get(member); if (proxy == null) throw new IllegalArgumentException("No such instance in this container."); try { InvocationHandler<T> handler = (InvocationHandler<T>) Proxy.getInvocationHandler(proxy); return handler.get_uid(); } catch (final Exception ex) { throw new IllegalArgumentException(ex); } } /** * Given a real object, return the Uid if it exists in this container. * * @param member the real object. * @return the Uid for the object if it exists in the container. * @exception throws IllegalArgumentException if the instance is not within the container. */ @SuppressWarnings(value={"unchecked"}) public Uid getUidForHandle (T proxy) { try { InvocationHandler<T> handler = (InvocationHandler<T>) Proxy.getInvocationHandler(proxy); return handler.get_uid(); } catch (final Exception ex) { throw new IllegalArgumentException("No such instance in this container.", ex); } } /** * Gives the name of the container. */ public String toString () { return "RecoverableContainer "+_name; } public final int objectType () { return _type; } public final int objectModel () { return _objectModel; } public final boolean isPessimistic (Object member) { // TODO change to use isAnnotationPresent? Class<?> c = member.getClass(); while (c != null) { if (c.getAnnotation(Optimistic.class) != null) { return false; } c = c.getSuperclass(); } Class<?>[] interfaces = member.getClass().getInterfaces(); for (Class<?> i : interfaces) { if (i.getAnnotation(Optimistic.class) != null) { return false; } } return true; } protected final void checkObjectType (Object member) { if ((member instanceof LockManager) || (member instanceof StateManager)) throw new IllegalArgumentException( "Object type not supported by this transactional container!"); Class<?> c = member.getClass(); while (c != null) { if (c.getAnnotation(Transactional.class) != null) { return; } c = c.getSuperclass(); } Class<?>[] interfaces = member.getClass().getInterfaces(); for (Class<?> i : interfaces) { if (i.getAnnotation(Transactional.class) != null) { return; } } throw new IllegalArgumentException("Object is not Lockable!"); } protected WeakHashMap<T, T> _transactionalProxies = new WeakHashMap<T, T>(); protected int _type; protected int _objectModel = ObjectModel.SINGLE; private final String _name; }