/** * 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(); + } diff --git a/source/java/org/alfresco/repo/policy/PolicyFactory.java b/source/java/org/alfresco/repo/policy/PolicyFactory.java index 661fb373f2..56ec034774 100644 --- a/source/java/org/alfresco/repo/policy/PolicyFactory.java +++ b/source/java/org/alfresco/repo/policy/PolicyFactory.java @@ -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
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
policy interface */ - @SuppressWarnings("hiding") - private static class MultiHandler
implements InvocationHandler, PolicyList + private static class MultiHandler
implements InvocationHandler, PolicyList { private Collection
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; + } } } diff --git a/source/java/org/alfresco/repo/policy/TransactionBehaviourQueue.java b/source/java/org/alfresco/repo/policy/TransactionBehaviourQueue.java new file mode 100644 index 0000000000..bf46e807aa --- /dev/null +++ b/source/java/org/alfresco/repo/policy/TransactionBehaviourQueue.java @@ -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
+ * @param behaviour + * @param definition + * @param policyInterface + * @param method + * @param args + */ + @SuppressWarnings("unchecked") + public
void queue(Behaviour behaviour, PolicyDefinition
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
(); + 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
+ */ + private class ExecutionContext
+ {
+ Method method;
+ Object[] args;
+ P policyInterface;
+ }
+
+
+ /**
+ * Queue Context
+ */
+ private class QueueContext
+ {
+ // TODO: Tune sizes
+ Queue
+ * @param behaviour
+ * @param definition
+ * @param policyInterface
+ * @return invocation handler
+ */
+ public InvocationHandler createHandler(Behaviour behaviour, PolicyDefinition definition, P policyInterface)
+ {
+ return new TransactionHandler (behaviour, definition, policyInterface);
+ }
+
+
+ /**
+ * Transaction Invocation Handler.
+ *
+ * @param policy interface
+ */
+ private class TransactionHandler implements InvocationHandler
+ {
+ private Behaviour behaviour;
+ private PolicyDefinition definition;
+ private P policyInterface;
+
+ /**
+ * Construct
+ */
+ public TransactionHandler(Behaviour behaviour, PolicyDefinition 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