Transaction-level Policies. Modify AuditableAspect and ContentHits example to make use of transaction policies so they only trigger once.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2651 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
David Caruana
2006-04-12 16:15:20 +00:00
parent 776314da72
commit 0330f2cdb7
14 changed files with 1102 additions and 71 deletions

View File

@@ -29,6 +29,17 @@ package org.alfresco.repo.policy;
*/
public interface Behaviour
{
/**
* When should behaviour be notified?
*/
public enum NotificationFrequency
{
EVERY_EVENT,
FIRST_EVENT,
TRANSACTION_COMMIT
}
/**
* Gets the requested policy interface onto the behaviour
*
@@ -51,5 +62,11 @@ public interface Behaviour
* @return is the behaviour enabled (for this thread only)
*/
public boolean isEnabled();
/**
* @return the notification
*/
public NotificationFrequency getNotificationFrequency();
}

View File

@@ -43,6 +43,9 @@ public class JavaBehaviour implements Behaviour
// The method name
private String method;
// Notification Frequency
private NotificationFrequency frequency;
// Cache of interface proxies (by interface class)
private Map<Class, Object> proxies = new HashMap<Class, Object>();
@@ -58,11 +61,23 @@ public class JavaBehaviour implements Behaviour
* @param method the method name
*/
public JavaBehaviour(Object instance, String method)
{
this(instance, method, NotificationFrequency.EVERY_EVENT);
}
/**
* Construct.
*
* @param instance the object instance holding the method
* @param method the method name
*/
public JavaBehaviour(Object instance, String method, NotificationFrequency frequency)
{
ParameterCheck.mandatory("Instance", instance);
ParameterCheck.mandatory("Method", method);
this.instance = instance;
this.method = method;
this.frequency = frequency;
}
@@ -114,6 +129,15 @@ public class JavaBehaviour implements Behaviour
return stack.search(hashCode()) == -1;
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.Behaviour#getNotificationFrequency()
*/
public NotificationFrequency getNotificationFrequency()
{
return frequency;
}
@Override
public String toString()
{

View File

@@ -30,4 +30,15 @@ public interface Policy
* derived policies
*/
static String NAMESPACE = NamespaceService.ALFRESCO_URI;
/**
* Argument Configuration
*/
public enum Arg
{
KEY,
START_VALUE,
END_VALUE
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.policy.Policy.Arg;
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
@@ -47,6 +48,7 @@ public class PolicyComponentImpl implements PolicyComponent
// Policy interface annotations
private static String ANNOTATION_NAMESPACE = "NAMESPACE";
private static String ANNOTATION_ARG ="ARG_";
// Dictionary Service
private DictionaryService dictionary;
@@ -94,6 +96,17 @@ public class PolicyComponentImpl implements PolicyComponent
}
/**
* Sets the transaction-based policy invocation handler
*
* @param factory
*/
public void setTransactionInvocationHandlerFactory(TransactionInvocationHandlerFactory factory)
{
PolicyFactory.setTransactionInvocationHandlerFactory(factory);
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.PolicyComponent#registerClassPolicy()
*/
@@ -509,8 +522,38 @@ public class PolicyComponentImpl implements PolicyComponent
}
String name = methods[0].getName();
// Extract Policy Arguments
Class[] paramTypes = methods[0].getParameterTypes();
Arg[] args = new Arg[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++)
{
// Extract Policy Arg
args[i] = (i == 0) ? Arg.KEY : Arg.START_VALUE;
try
{
Field argMetadata = policyIF.getField(ANNOTATION_ARG + i);
if (!Arg.class.isAssignableFrom(argMetadata.getType()))
{
throw new PolicyException("ARG_" + i + " metadata incorrectly specified in policy " + policyIF.getCanonicalName());
}
args[i] = (Arg)argMetadata.get(null);
if (i == 0 && (!args[i].equals(Arg.KEY)))
{
throw new PolicyException("ARG_" + i + " specified in policy " + policyIF.getCanonicalName() + " must be a key");
}
}
catch(NoSuchFieldException e)
{
// Assume default ARG configuration
}
catch(IllegalAccessException e)
{
// Shouldn't get here (interface definitions must be accessible)
}
}
// Create Policy Definition
return new PolicyDefinitionImpl(QName.createQName(namespaceURI, name), policyIF);
return new PolicyDefinitionImpl(QName.createQName(namespaceURI, name), policyIF, args);
}
@@ -564,11 +607,13 @@ public class PolicyComponentImpl implements PolicyComponent
{
private QName policy;
private Class policyIF;
private Arg[] args;
/*package*/ PolicyDefinitionImpl(QName policy, Class policyIF)
/*package*/ PolicyDefinitionImpl(QName policy, Class policyIF, Arg[] args)
{
this.policy = policy;
this.policyIF = policyIF;
this.args = args;
}
/* (non-Javadoc)
@@ -605,6 +650,27 @@ public class PolicyComponentImpl implements PolicyComponent
return PolicyType.Association;
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.PolicyDefinition#getArgument(int)
*/
public Arg getArgument(int index)
{
if (index < 0 || index > args.length -1)
{
throw new IllegalArgumentException("Argument index " + index + " is invalid");
}
return args[index];
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.PolicyDefinition#getArguments()
*/
public Arg[] getArguments()
{
return args;
}
}

View File

@@ -0,0 +1,417 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.alfresco.repo.policy;
import java.util.ArrayList;
import java.util.List;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.repo.dictionary.DictionaryBootstrap;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.Policy.Arg;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* Test Transaction-level Policies
*/
public class PolicyComponentTransactionTest extends TestCase
{
private static final String TEST_MODEL = "org/alfresco/repo/policy/policycomponenttest_model.xml";
private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/policycomponenttest/1.0";
private static QName BASE_TYPE = QName.createQName(TEST_NAMESPACE, "base");
private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext();
private PolicyComponent policyComponent;
private TransactionService trxService;
private AuthenticationComponent authenticationComponent;
private ClassPolicyDelegate<SideEffectTestPolicy> sideEffectDelegate;
@Override
protected void setUp() throws Exception
{
// initialise policy test model
DictionaryBootstrap bootstrap = new DictionaryBootstrap();
List<String> bootstrapModels = new ArrayList<String>();
bootstrapModels.add(TEST_MODEL);
bootstrap.setModels(bootstrapModels);
bootstrap.setDictionaryDAO((DictionaryDAO)applicationContext.getBean("dictionaryDAO"));
bootstrap.bootstrap();
// retrieve policy component
this.policyComponent = (PolicyComponent)applicationContext.getBean("policyComponent");
this.trxService = (TransactionService) applicationContext.getBean("transactionComponent");
this.authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent");
this.authenticationComponent.setSystemUserAsCurrentUser();
// Register Policy
sideEffectDelegate = policyComponent.registerClassPolicy(SideEffectTestPolicy.class);
// Bind Behaviour to side effect policy
QName policyName = QName.createQName(TEST_NAMESPACE, "sideEffect");
Behaviour baseBehaviour = new JavaBehaviour(this, "sideEffectTest", NotificationFrequency.TRANSACTION_COMMIT);
policyComponent.bindClassBehaviour(policyName, BASE_TYPE, baseBehaviour);
}
@Override
protected void tearDown() throws Exception
{
authenticationComponent.clearCurrentSecurityContext();
}
public void testStartTransactionPolicy()
throws Exception
{
ClassPolicyDelegate<StartTestPolicy> startDelegate = policyComponent.registerClassPolicy(StartTestPolicy.class);
// Register Policy
QName policyName = QName.createQName(TEST_NAMESPACE, "start");
PolicyDefinition definition = policyComponent.getRegisteredPolicy(PolicyType.Class, policyName);
assertNotNull(definition);
Arg arg0 = definition.getArgument(0);
assertEquals(Arg.KEY, arg0);
Arg arg1 = definition.getArgument(1);
assertEquals(Arg.KEY, arg1);
Arg arg2 = definition.getArgument(2);
assertEquals(Arg.START_VALUE, arg2);
Arg arg3 = definition.getArgument(3);
assertEquals(Arg.END_VALUE, arg3);
// Bind Behaviour
Behaviour baseBehaviour = new JavaBehaviour(this, "startTransactionTest", NotificationFrequency.FIRST_EVENT);
policyComponent.bindClassBehaviour(policyName, BASE_TYPE, baseBehaviour);
// Invoke Behaviour
UserTransaction userTransaction1 = trxService.getUserTransaction();
try
{
userTransaction1.begin();
List<TestResult> results = new ArrayList<TestResult>();
StartTestPolicy basePolicy = startDelegate.get(BASE_TYPE);
String baseResult1 = basePolicy.start("1", "2", "value1a", "value2a", false, results);
TestResult result1 = new TestResult("startTransactionTest", "1", "2", "value1a", "value2a");
assertEquals(result1.toString(), baseResult1);
assertEquals(1, results.size());
assertEquals(result1, results.get(0));
String baseResult2 = basePolicy.start("2", "1", "value1b", "value2b", false, results);
TestResult result2 = new TestResult("startTransactionTest", "2", "1", "value1b", "value2b");
assertEquals(result2.toString(), baseResult2);
assertEquals(2, results.size());
assertEquals(result2, results.get(1));
String baseResult3 = basePolicy.start("1", "2", "value1c", "value2c", false, results);
assertEquals(result1.toString(), baseResult3);
assertEquals(2, results.size());
userTransaction1.commit();
assertEquals(2, results.size());
assertEquals(result1, results.get(0));
assertEquals(result2, results.get(1));
}
catch(Exception e)
{
try { userTransaction1.rollback(); } catch (IllegalStateException ee) {}
throw e;
}
// Invoke Behaviour
UserTransaction userTransaction2 = trxService.getUserTransaction();
try
{
userTransaction2.begin();
List<TestResult> results = new ArrayList<TestResult>();
StartTestPolicy basePolicy = startDelegate.get(BASE_TYPE);
String baseResult1 = basePolicy.start("1", "2", "value1a", "value2a", true, results);
TestResult result1 = new TestResult("startTransactionTest", "1", "2", "value1a", "value2a");
assertEquals(result1.toString(), baseResult1);
assertEquals(1, results.size());
assertEquals(result1, results.get(0));
String baseResult2 = basePolicy.start("2", "1", "value1b", "value2b", true, results);
TestResult result2 = new TestResult("startTransactionTest", "2", "1", "value1b", "value2b");
assertEquals(result2.toString(), baseResult2);
assertEquals(2, results.size());
assertEquals(result2, results.get(1));
String baseResult3 = basePolicy.start("1", "2", "value1c", "value2c", true, results);
assertEquals(result1.toString(), baseResult3);
assertEquals(2, results.size());
TestResult result3 = new TestResult("sideEffectTest", "1", "2", "value1a", "value2a");
TestResult result4 = new TestResult("sideEffectTest", "2", "1", "value1b", "value2b");
userTransaction2.commit();
assertEquals(4, results.size());
assertEquals(result1, results.get(0));
assertEquals(result2, results.get(1));
assertEquals(result3, results.get(2));
assertEquals(result4, results.get(3));
}
catch(Exception e)
{
try { userTransaction2.rollback(); } catch (IllegalStateException ee) {}
throw e;
}
}
public void testEndTransactionPolicy()
throws Exception
{
ClassPolicyDelegate<EndTestPolicy> endDelegate = policyComponent.registerClassPolicy(EndTestPolicy.class);
QName policyName = QName.createQName(TEST_NAMESPACE, "end");
PolicyDefinition definition = policyComponent.getRegisteredPolicy(PolicyType.Class, policyName);
assertNotNull(definition);
Arg arg0 = definition.getArgument(0);
assertEquals(Arg.KEY, arg0);
Arg arg1 = definition.getArgument(1);
assertEquals(Arg.KEY, arg1);
Arg arg2 = definition.getArgument(2);
assertEquals(Arg.START_VALUE, arg2);
Arg arg3 = definition.getArgument(3);
assertEquals(Arg.END_VALUE, arg3);
// Bind Behaviour
Behaviour baseBehaviour = new JavaBehaviour(this, "endTransactionTest", NotificationFrequency.TRANSACTION_COMMIT);
policyComponent.bindClassBehaviour(policyName, BASE_TYPE, baseBehaviour);
UserTransaction userTransaction1 = trxService.getUserTransaction();
try
{
userTransaction1.begin();
List<TestResult> results = new ArrayList<TestResult>();
// Invoke Behaviour
EndTestPolicy basePolicy = endDelegate.get(BASE_TYPE);
String baseResult1 = basePolicy.end("1", "2", "value1a", "value2a", false, results);
assertEquals(null, baseResult1);
assertEquals(0, results.size());
String baseResult2 = basePolicy.end("2", "1", "value1b", "value2b", false, results);
assertEquals(null, baseResult2);
assertEquals(0, results.size());
String baseResult3 = basePolicy.end("1", "2", "value1a", "value2c", false, results);
assertEquals(null, baseResult3);
assertEquals(0, results.size());
TestResult result1 = new TestResult("endTransactionTest", "1", "2", "value1a", "value2c");
TestResult result2 = new TestResult("endTransactionTest", "2", "1", "value1b", "value2b");
userTransaction1.commit();
assertEquals(2, results.size());
assertEquals(result1, results.get(0));
assertEquals(result2, results.get(1));
}
catch(Exception e)
{
try { userTransaction1.rollback(); } catch (IllegalStateException ee) {}
throw e;
}
UserTransaction userTransaction2 = trxService.getUserTransaction();
try
{
userTransaction2.begin();
List<TestResult> results = new ArrayList<TestResult>();
// Invoke Behaviour
EndTestPolicy basePolicy = endDelegate.get(BASE_TYPE);
String baseResult1 = basePolicy.end("1", "2", "value1a", "value2a", true, results);
assertEquals(null, baseResult1);
assertEquals(0, results.size());
String baseResult2 = basePolicy.end("2", "1", "value1b", "value2b", true, results);
assertEquals(null, baseResult2);
assertEquals(0, results.size());
String baseResult3 = basePolicy.end("1", "2", "value1a", "value2c", true, results);
assertEquals(null, baseResult3);
assertEquals(0, results.size());
TestResult result1 = new TestResult("endTransactionTest", "1", "2", "value1a", "value2c");
TestResult result2 = new TestResult("endTransactionTest", "2", "1", "value1b", "value2b");
TestResult result3 = new TestResult("sideEffectTest", "1", "2", "value1a", "value2c");
TestResult result4 = new TestResult("sideEffectTest", "2", "1", "value1b", "value2b");
userTransaction2.commit();
assertEquals(4, results.size());
assertEquals(result1, results.get(0));
assertEquals(result2, results.get(1));
assertEquals(result3, results.get(2));
assertEquals(result4, results.get(3));
}
catch(Exception e)
{
try { userTransaction2.rollback(); } catch (IllegalStateException ee) {}
throw e;
}
}
//
// Behaviour Implementations
//
public String startTransactionTest(String key1, String key2, String arg1, String arg2, boolean sideEffect, List<TestResult> results)
{
TestResult result = new TestResult("startTransactionTest", key1, key2, arg1, arg2);
results.add(result);
if (sideEffect)
{
SideEffectTestPolicy policy = sideEffectDelegate.get(BASE_TYPE);
policy.sideEffect(key1, key2, arg1, arg2, results);
}
return result.toString();
}
public String endTransactionTest(String key1, String key2, String arg1, String arg2, boolean sideEffect, List<TestResult> results)
{
TestResult result = new TestResult("endTransactionTest", key1, key2, arg1, arg2);
results.add(result);
if (sideEffect)
{
SideEffectTestPolicy policy = sideEffectDelegate.get(BASE_TYPE);
policy.sideEffect(key1, key2, arg1, arg2, results);
}
return result.toString();
}
public String sideEffectTest(String key1, String key2, String arg1, String arg2, List<TestResult> results)
{
TestResult result = new TestResult("sideEffectTest", key1, key2, arg1, arg2);
results.add(result);
return result.toString();
}
//
// Policy Definitions
//
public interface StartTestPolicy extends ClassPolicy
{
public String start(String key1, String key2, String arg1, String arg2, boolean sideEffect, List<TestResult> results);
static String NAMESPACE = TEST_NAMESPACE;
static Arg ARG_0 = Arg.KEY;
static Arg ARG_1 = Arg.KEY;
static Arg ARG_2 = Arg.START_VALUE;
static Arg ARG_3 = Arg.END_VALUE;
}
public interface EndTestPolicy extends ClassPolicy
{
public String end(String key1, String key2, String arg1, String arg2, boolean sideEffect, List<TestResult> results);
static String NAMESPACE = TEST_NAMESPACE;
static Arg ARG_0 = Arg.KEY;
static Arg ARG_1 = Arg.KEY;
static Arg ARG_2 = Arg.START_VALUE;
static Arg ARG_3 = Arg.END_VALUE;
}
public interface SideEffectTestPolicy extends ClassPolicy
{
public String sideEffect(String key1, String key2, String arg1, String arg2, List<TestResult> resultTest);
static String NAMESPACE = TEST_NAMESPACE;
static Arg ARG_0 = Arg.KEY;
static Arg ARG_1 = Arg.KEY;
}
/**
* Result of Policy Invocation
*/
private class TestResult
{
private String trxId;
private String behaviour;
private String key1;
private String key2;
private String arg1;
private String arg2;
/**
* Construct
*
* @param behaviour
* @param key1
* @param key2
* @param arg1
* @param arg2
*/
public TestResult(String behaviour, String key1, String key2, String arg1, String arg2)
{
this.trxId = AlfrescoTransactionSupport.getTransactionId();
this.behaviour = behaviour;
this.key1 = key1;
this.key2 = key2;
this.arg1 = arg1;
this.arg2 = arg2;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj instanceof TestResult)
{
TestResult that = (TestResult) obj;
return (this.trxId.equals(that.trxId) &&
this.behaviour.equals(that.behaviour) &&
this.key1.equals(that.key1) &&
this.key2.equals(that.key2) &&
this.arg1.equals(that.arg1) &&
this.arg2.equals(that.arg2));
}
else
{
return false;
}
}
@Override
public String toString()
{
return "trxId=" + trxId + ", behaviour=" + behaviour + ", key1=" + key1 + ", key2=" + key2 + ", arg1=" + arg1 + ", arg2=" + arg2;
}
}
}

View File

@@ -16,6 +16,7 @@
*/
package org.alfresco.repo.policy;
import org.alfresco.repo.policy.Policy.Arg;
import org.alfresco.service.namespace.QName;
@@ -46,7 +47,23 @@ public interface PolicyDefinition<P extends Policy>
/**
* Gets the Policy type
*
* @return the policy type
*/
public PolicyType getType();
/**
* Gets Policy Argument definition for the specified argument index
*
* @param index argument index
* @return ARG.KEY or ARG.START_VALUE or ARG.END_VALUE
*/
public Arg getArgument(int index);
/**
* Gets Policy Argument definitions for all arguments in order of arguments
* @return
*/
public Arg[] getArguments();
}

View File

@@ -25,6 +25,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
/**
* A Policy Factory is responsible for creating Policy implementations.
@@ -41,6 +43,12 @@ import java.util.List;
// The policy interface class
private Class<P> policyClass;
// NOOP Invocation Handler
private static InvocationHandler NOOPHandler = new NOOPHandler();
// Transaction Invocation Handler Factory
private static TransactionInvocationHandlerFactory transactionHandlerFactory = null;
/**
@@ -55,6 +63,17 @@ import java.util.List;
this.index = index;
}
/**
* Sets the Transaction Invocation Handler
*
* @param handlerFactory
*/
protected static void setTransactionInvocationHandlerFactory(TransactionInvocationHandlerFactory factory)
{
transactionHandlerFactory = factory;
}
/**
* Gets the Policy class created by this factory
@@ -95,6 +114,16 @@ import java.util.List;
{
Behaviour behaviour = behaviourDef.getBehaviour();
P policyIF = behaviour.getInterface(policyClass);
if (!(behaviour.getNotificationFrequency().equals(NotificationFrequency.EVERY_EVENT)))
{
// wrap behaviour in transaction proxy which deals with delaying invocation until necessary
if (transactionHandlerFactory == null)
{
throw new PolicyException("Transaction-level policies not supported as transaction support for the Policy Component has not been initialised.");
}
InvocationHandler trxHandler = transactionHandlerFactory.createHandler(behaviour, behaviourDef.getPolicyDefinition(), policyIF);
policyIF = (P)Proxy.newProxyInstance(policyClass.getClassLoader(), new Class[]{policyClass}, trxHandler);
}
policyInterfaces.add(policyIF);
}
@@ -119,7 +148,7 @@ import java.util.List;
else if (policyList.size() == 0)
{
return (P)Proxy.newProxyInstance(policyClass.getClassLoader(),
new Class[]{policyClass}, new NOOPHandler());
new Class[]{policyClass}, NOOPHandler);
}
else
{
@@ -157,7 +186,7 @@ import java.util.List;
return null;
}
}
/**
* Multi-policy Invocation Handler.
@@ -166,8 +195,7 @@ import java.util.List;
*
* @param <P> policy interface
*/
@SuppressWarnings("hiding")
private static class MultiHandler<P> implements InvocationHandler, PolicyList
private static class MultiHandler<P extends Policy> implements InvocationHandler, PolicyList
{
private Collection<P> policyInterfaces;
@@ -186,13 +214,13 @@ import java.util.List;
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// Handle PolicyList level methods
if (method.getDeclaringClass().equals(PolicyList.class))
{
return method.invoke(this, args);
}
// Handle Object level methods
// Handle PolicyList level methods
if (method.getDeclaringClass().equals(PolicyList.class))
{
return method.invoke(this, args);
}
// Handle Object level methods
if (method.getName().equals("toString"))
{
return toString() + ": wrapped " + policyInterfaces.size() + " policies";
@@ -222,13 +250,13 @@ import java.util.List;
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.PolicyList#getPolicies()
*/
public Collection getPolicies()
{
return policyInterfaces;
}
/* (non-Javadoc)
* @see org.alfresco.repo.policy.PolicyList#getPolicies()
*/
public Collection getPolicies()
{
return policyInterfaces;
}
}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.alfresco.repo.policy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.policy.Policy.Arg;
import org.alfresco.repo.rule.RuleTransactionListener;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListener;
import org.alfresco.util.GUID;
/**
* Transaction Behaviour Queue.
*
* Responsible for keeping a record of behaviours to execute at the end of a transaction.
*/
public class TransactionBehaviourQueue implements TransactionListener
{
/** Id used in equals and hash */
private String id = GUID.generate();
// Transaction Keys for Behaviour Execution state
private static final String QUEUE_CONTEXT_KEY = TransactionBehaviourQueue.class.getName() + ".context";
/**
* Queue a behaviour for end-of-transaction execution
*
* @param <P>
* @param behaviour
* @param definition
* @param policyInterface
* @param method
* @param args
*/
@SuppressWarnings("unchecked")
public <P extends Policy> void queue(Behaviour behaviour, PolicyDefinition<P> definition, P policyInterface, Method method, Object[] args)
{
// Construct queue context, if required
QueueContext queueContext = (QueueContext)AlfrescoTransactionSupport.getResource(QUEUE_CONTEXT_KEY);
if (queueContext == null)
{
queueContext = new QueueContext();
AlfrescoTransactionSupport.bindResource(QUEUE_CONTEXT_KEY, queueContext);
AlfrescoTransactionSupport.bindListener(this);
}
// Determine if behaviour instance has already been queued
Integer instanceKey = createInstanceKey(behaviour, definition.getArguments(), args);
ExecutionContext executionContext = queueContext.index.get(instanceKey);
if (executionContext == null)
{
// Create execution context for behaviour
executionContext = new ExecutionContext<P>();
executionContext.method = method;
executionContext.args = args;
executionContext.policyInterface = policyInterface;
// Defer or execute now?
if (!queueContext.committed)
{
// queue behaviour for deferred execution
queueContext.queue.offer(executionContext);
}
else
{
// execute now
execute(executionContext);
}
queueContext.index.put(instanceKey, executionContext);
}
else
{
// Update behaviour instance execution context, in particular, argument state that is marked END_TRANSACTION
Arg[] argDefs = definition.getArguments();
for (int i = 0; i < argDefs.length; i++)
{
if (argDefs[i].equals(Arg.END_VALUE))
{
executionContext.args[i] = args[i];
}
}
}
}
/* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#flush()
*/
public void flush()
{
}
/* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#beforeCommit(boolean)
*/
@SuppressWarnings("unchecked")
public void beforeCommit(boolean readOnly)
{
QueueContext queueContext = (QueueContext)AlfrescoTransactionSupport.getResource(QUEUE_CONTEXT_KEY);
ExecutionContext context = queueContext.queue.poll();
while (context != null)
{
execute(context);
context = queueContext.queue.poll();
}
queueContext.committed = true;
}
/* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#beforeCompletion()
*/
public void beforeCompletion()
{
}
/* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#afterCommit()
*/
public void afterCommit()
{
}
/* (non-Javadoc)
* @see org.alfresco.repo.transaction.TransactionListener#afterRollback()
*/
public void afterRollback()
{
}
/**
* Create an instance key for the behaviour based on the "key" arguments passed in
*
* @param argDefs definitions of behaviour arguments
* @param args the argument values
* @return the key
*/
private Integer createInstanceKey(Behaviour behaviour, Arg[] argDefs, Object[] args)
{
int key = behaviour.hashCode();
for (int i = 0; i < argDefs.length; i++)
{
if (argDefs[i].equals(Arg.KEY))
{
key = (37 * key) + args[i].hashCode();
}
}
return new Integer(key);
}
/**
* Execute behaviour as described in execution context
*
* @param context
*/
private void execute(ExecutionContext context)
{
try
{
context.method.invoke(context.policyInterface, context.args);
}
catch (IllegalArgumentException e)
{
throw new AlfrescoRuntimeException("Failed to execute transaction-level behaviour " + context.method + " in transaction " + AlfrescoTransactionSupport.getTransactionId(), e);
}
catch (IllegalAccessException e)
{
throw new AlfrescoRuntimeException("Failed to execute transaction-level behaviour " + context.method + " in transaction " + AlfrescoTransactionSupport.getTransactionId(), e);
}
catch (InvocationTargetException e)
{
throw new AlfrescoRuntimeException("Failed to execute transaction-level behaviour " + context.method + " in transaction " + AlfrescoTransactionSupport.getTransactionId(), e.getTargetException());
}
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return this.id.hashCode();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj instanceof RuleTransactionListener)
{
TransactionBehaviourQueue that = (TransactionBehaviourQueue) obj;
return (this.id.equals(that.id));
}
else
{
return false;
}
}
/**
* Behaviour execution Context
*
* @param <P>
*/
private class ExecutionContext<P extends Policy>
{
Method method;
Object[] args;
P policyInterface;
}
/**
* Queue Context
*/
private class QueueContext
{
// TODO: Tune sizes
Queue<ExecutionContext> queue = new LinkedList<ExecutionContext>();
Map<Integer, ExecutionContext> index = new HashMap<Integer, ExecutionContext>();
boolean committed = false;
}
}

View File

@@ -0,0 +1,177 @@
/*
* Copyright (C) 2005 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.alfresco.repo.policy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
import org.alfresco.repo.policy.Policy.Arg;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
/**
* Factory for creating transaction-aware behaviour invocation handlers.
*/
public class TransactionInvocationHandlerFactory
{
/** Transaction Key for Behaviour Execution state */
private final static String EXECUTED_KEY = TransactionHandler.class.getName() + ".executed";
/** Transaction behaviour Queue */
private TransactionBehaviourQueue queue;
/**
* Construct
*
* @param queue behaviour queue
*/
public TransactionInvocationHandlerFactory(TransactionBehaviourQueue queue)
{
this.queue = queue;
}
/**
* Create Invocation Handler
*
* @param <P>
* @param behaviour
* @param definition
* @param policyInterface
* @return invocation handler
*/
public <P extends Policy> InvocationHandler createHandler(Behaviour behaviour, PolicyDefinition<P> definition, P policyInterface)
{
return new TransactionHandler<P>(behaviour, definition, policyInterface);
}
/**
* Transaction Invocation Handler.
*
* @param <P> policy interface
*/
private class TransactionHandler<P extends Policy> implements InvocationHandler
{
private Behaviour behaviour;
private PolicyDefinition<P> definition;
private P policyInterface;
/**
* Construct
*/
public TransactionHandler(Behaviour behaviour, PolicyDefinition<P> definition, P policyInterface)
{
this.behaviour = behaviour;
this.definition = definition;
this.policyInterface = policyInterface;
}
/* (non-Javadoc)
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
@SuppressWarnings("unchecked")
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// Handle Object level methods
if (method.getName().equals("toString"))
{
return policyInterface.toString();
}
else if (method.getName().equals("hashCode"))
{
return policyInterface.hashCode();
}
else if (method.getName().equals("equals"))
{
return policyInterface.equals(args[0]);
}
// Invoke policy based on its notification frequency
Object result = null;
if (behaviour.getNotificationFrequency().equals(NotificationFrequency.FIRST_EVENT))
{
Map<Integer, Object> executedBehaviours = (Map<Integer, Object>)AlfrescoTransactionSupport.getResource(EXECUTED_KEY);
if (executedBehaviours == null)
{
executedBehaviours = new HashMap<Integer, Object>();
AlfrescoTransactionSupport.bindResource(EXECUTED_KEY, executedBehaviours);
}
Integer behaviourKey = createInstanceKey(behaviour, definition.getArguments(), args);
if (executedBehaviours.containsKey(behaviourKey) == false)
{
// Invoke behavior for first time and mark as executed
try
{
result = method.invoke(policyInterface, args);
executedBehaviours.put(behaviourKey, result);
}
catch (InvocationTargetException e)
{
throw e.getTargetException();
}
}
else
{
// Return result of previous execution
result = executedBehaviours.get(behaviourKey);
}
}
else if (behaviour.getNotificationFrequency().equals(NotificationFrequency.TRANSACTION_COMMIT))
{
// queue policy invocation for end of transaction
queue.queue(behaviour, definition, policyInterface, method, args);
}
else
{
// Note: shouldn't get here
throw new PolicyException("Invalid Notification frequency " + behaviour.getNotificationFrequency());
}
return result;
}
/**
* Create an instance key for the behaviour based on the "key" arguments passed in
*
* @param argDefs definitions of behaviour arguments
* @param args the argument values
* @return the key
*/
private Integer createInstanceKey(Behaviour behaviour, Arg[] argDefs, Object[] args)
{
int key = behaviour.hashCode();
for (int i = 0; i < argDefs.length; i++)
{
if (argDefs[i].equals(Arg.KEY))
{
key = (37 * key) + args[i].hashCode();
}
}
return new Integer(key);
}
}
}