mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
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:
@@ -19,6 +19,7 @@
|
|||||||
<import resource="classpath:alfresco/index-recovery-context.xml" />
|
<import resource="classpath:alfresco/index-recovery-context.xml" />
|
||||||
<import resource="classpath:alfresco/authority-services-context.xml" />
|
<import resource="classpath:alfresco/authority-services-context.xml" />
|
||||||
<import resource="classpath:alfresco/authentication-services-context.xml" />
|
<import resource="classpath:alfresco/authentication-services-context.xml" />
|
||||||
|
<import resource="classpath:alfresco/policy-context.xml" />
|
||||||
<import resource="classpath:alfresco/import-export-context.xml" />
|
<import resource="classpath:alfresco/import-export-context.xml" />
|
||||||
<import resource="classpath:alfresco/bootstrap-context.xml" />
|
<import resource="classpath:alfresco/bootstrap-context.xml" />
|
||||||
<import resource="classpath*:alfresco/patch/*-context.xml" />
|
<import resource="classpath*:alfresco/patch/*-context.xml" />
|
||||||
|
@@ -531,23 +531,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- Policy Support -->
|
|
||||||
|
|
||||||
<bean id="policyBehaviourFilter" class="org.alfresco.repo.policy.BehaviourFilterImpl">
|
|
||||||
<property name="dictionaryService">
|
|
||||||
<ref bean="dictionaryService"/>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<bean id="policyComponent" class="org.alfresco.repo.policy.PolicyComponentImpl">
|
|
||||||
<constructor-arg index="0">
|
|
||||||
<ref bean="dictionaryService"/>
|
|
||||||
</constructor-arg>
|
|
||||||
<property name="behaviourFilter">
|
|
||||||
<ref bean="policyBehaviourFilter"/>
|
|
||||||
</property>
|
|
||||||
</bean>
|
|
||||||
|
|
||||||
<!-- Bean to support full text search -->
|
<!-- Bean to support full text search -->
|
||||||
|
|
||||||
<bean id="LuceneFullTextSearchIndexer"
|
<bean id="LuceneFullTextSearchIndexer"
|
||||||
|
34
config/alfresco/policy-context.xml
Normal file
34
config/alfresco/policy-context.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||||
|
|
||||||
|
<beans>
|
||||||
|
<!-- Policy Support -->
|
||||||
|
|
||||||
|
<bean id="policyBehaviourQueue" class="org.alfresco.repo.policy.TransactionBehaviourQueue">
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="policyTransactionHandlerFactory" class="org.alfresco.repo.policy.TransactionInvocationHandlerFactory">
|
||||||
|
<constructor-arg index="0">
|
||||||
|
<ref bean="policyBehaviourQueue"/>
|
||||||
|
</constructor-arg>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="policyBehaviourFilter" class="org.alfresco.repo.policy.BehaviourFilterImpl">
|
||||||
|
<property name="dictionaryService">
|
||||||
|
<ref bean="dictionaryService"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="policyComponent" class="org.alfresco.repo.policy.PolicyComponentImpl">
|
||||||
|
<constructor-arg index="0">
|
||||||
|
<ref bean="dictionaryService"/>
|
||||||
|
</constructor-arg>
|
||||||
|
<property name="behaviourFilter">
|
||||||
|
<ref bean="policyBehaviourFilter"/>
|
||||||
|
</property>
|
||||||
|
<property name="transactionInvocationHandlerFactory">
|
||||||
|
<ref bean="policyTransactionHandlerFactory"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
</beans>
|
@@ -26,7 +26,9 @@ import org.alfresco.repo.policy.BehaviourFilter;
|
|||||||
import org.alfresco.repo.policy.JavaBehaviour;
|
import org.alfresco.repo.policy.JavaBehaviour;
|
||||||
import org.alfresco.repo.policy.PolicyComponent;
|
import org.alfresco.repo.policy.PolicyComponent;
|
||||||
import org.alfresco.repo.policy.PolicyScope;
|
import org.alfresco.repo.policy.PolicyScope;
|
||||||
|
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
|
||||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeService;
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
@@ -100,9 +102,9 @@ public class AuditableAspect
|
|||||||
public void init()
|
public void init()
|
||||||
{
|
{
|
||||||
// Create behaviours
|
// Create behaviours
|
||||||
onCreateAudit = new JavaBehaviour(this, "onCreateAudit");
|
onCreateAudit = new JavaBehaviour(this, "onCreateAudit", NotificationFrequency.FIRST_EVENT);
|
||||||
onAddAudit = new JavaBehaviour(this, "onAddAudit");
|
onAddAudit = new JavaBehaviour(this, "onAddAudit", NotificationFrequency.FIRST_EVENT);
|
||||||
onUpdateAudit = new JavaBehaviour(this, "onUpdateAudit");
|
onUpdateAudit = new JavaBehaviour(this, "onUpdateAudit", NotificationFrequency.TRANSACTION_COMMIT);
|
||||||
|
|
||||||
// Bind behaviours to node policies
|
// Bind behaviours to node policies
|
||||||
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.ASPECT_AUDITABLE, onCreateAudit);
|
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.ASPECT_AUDITABLE, onCreateAudit);
|
||||||
@@ -132,37 +134,36 @@ public class AuditableAspect
|
|||||||
*/
|
*/
|
||||||
public void onAddAudit(NodeRef nodeRef, QName aspect)
|
public void onAddAudit(NodeRef nodeRef, QName aspect)
|
||||||
{
|
{
|
||||||
|
// Get the current properties
|
||||||
|
Map<QName, Serializable> properties = this.nodeService.getProperties(nodeRef);
|
||||||
|
|
||||||
|
// Set created / updated date
|
||||||
|
Date now = new Date(System.currentTimeMillis());
|
||||||
|
properties.put(ContentModel.PROP_CREATED, now);
|
||||||
|
properties.put(ContentModel.PROP_MODIFIED, now);
|
||||||
|
|
||||||
|
// Set creator (but do not override, if explicitly set)
|
||||||
|
String creator = (String)properties.get(ContentModel.PROP_CREATOR);
|
||||||
|
if (creator == null || creator.length() == 0)
|
||||||
|
{
|
||||||
|
creator = getUsername();
|
||||||
|
properties.put(ContentModel.PROP_CREATOR, creator);
|
||||||
|
}
|
||||||
|
properties.put(ContentModel.PROP_MODIFIER, creator);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
// Set the updated property values (but do not cascade to update audit behaviour)
|
||||||
|
onUpdateAudit.disable();
|
||||||
// Get the current properties
|
|
||||||
Map<QName, Serializable> properties = this.nodeService.getProperties(nodeRef);
|
|
||||||
|
|
||||||
// Set created / updated date
|
|
||||||
Date now = new Date(System.currentTimeMillis());
|
|
||||||
properties.put(ContentModel.PROP_CREATED, now);
|
|
||||||
properties.put(ContentModel.PROP_MODIFIED, now);
|
|
||||||
|
|
||||||
// Set creator (but do not override, if explicitly set)
|
|
||||||
String creator = (String)properties.get(ContentModel.PROP_CREATOR);
|
|
||||||
if (creator == null || creator.length() == 0)
|
|
||||||
{
|
|
||||||
creator = getUsername();
|
|
||||||
properties.put(ContentModel.PROP_CREATOR, creator);
|
|
||||||
}
|
|
||||||
properties.put(ContentModel.PROP_MODIFIER, creator);
|
|
||||||
|
|
||||||
// Set the updated property values
|
|
||||||
this.nodeService.setProperties(nodeRef, properties);
|
this.nodeService.setProperties(nodeRef, properties);
|
||||||
|
|
||||||
if (logger.isDebugEnabled())
|
|
||||||
logger.debug("Auditable node " + nodeRef + " created [created,modified=" + now + ";creator,modifier=" + creator + "]");
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
onUpdateAudit.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Auditable node " + nodeRef + " created [created,modified=" + now + ";creator,modifier=" + creator + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,11 +173,9 @@ public class AuditableAspect
|
|||||||
*/
|
*/
|
||||||
public void onUpdateAudit(NodeRef nodeRef)
|
public void onUpdateAudit(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
|
// Get the current properties
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
|
||||||
|
|
||||||
// Get the current properties
|
|
||||||
Map<QName, Serializable> properties = this.nodeService.getProperties(nodeRef);
|
Map<QName, Serializable> properties = this.nodeService.getProperties(nodeRef);
|
||||||
|
|
||||||
// Set updated date
|
// Set updated date
|
||||||
@@ -193,9 +192,10 @@ public class AuditableAspect
|
|||||||
if (logger.isDebugEnabled())
|
if (logger.isDebugEnabled())
|
||||||
logger.debug("Auditable node " + nodeRef + " updated [modified=" + now + ";modifier=" + modifier + "]");
|
logger.debug("Auditable node " + nodeRef + " updated [modified=" + now + ";modifier=" + modifier + "]");
|
||||||
}
|
}
|
||||||
finally
|
catch(InvalidNodeRefException e)
|
||||||
{
|
{
|
||||||
this.policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
|
if (logger.isDebugEnabled())
|
||||||
|
logger.debug("Warning: Auditable node " + nodeRef + " no longer exists - cannot update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,8 +21,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.alfresco.repo.policy.AssociationPolicy;
|
import org.alfresco.repo.policy.AssociationPolicy;
|
||||||
import org.alfresco.repo.policy.ClassPolicy;
|
import org.alfresco.repo.policy.ClassPolicy;
|
||||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
|
||||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.StoreRef;
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
@@ -117,6 +117,10 @@ public interface NodeServicePolicies
|
|||||||
NodeRef nodeRef,
|
NodeRef nodeRef,
|
||||||
Map<QName, Serializable> before,
|
Map<QName, Serializable> before,
|
||||||
Map<QName, Serializable> after);
|
Map<QName, Serializable> after);
|
||||||
|
|
||||||
|
static Arg ARG_0 = Arg.KEY;
|
||||||
|
static Arg ARG_1 = Arg.START_VALUE;
|
||||||
|
static Arg ARG_2 = Arg.END_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface BeforeDeleteNodePolicy extends ClassPolicy
|
public interface BeforeDeleteNodePolicy extends ClassPolicy
|
||||||
|
@@ -29,6 +29,17 @@ package org.alfresco.repo.policy;
|
|||||||
*/
|
*/
|
||||||
public interface Behaviour
|
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
|
* Gets the requested policy interface onto the behaviour
|
||||||
*
|
*
|
||||||
@@ -52,4 +63,10 @@ public interface Behaviour
|
|||||||
*/
|
*/
|
||||||
public boolean isEnabled();
|
public boolean isEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the notification
|
||||||
|
*/
|
||||||
|
public NotificationFrequency getNotificationFrequency();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -44,6 +44,9 @@ public class JavaBehaviour implements Behaviour
|
|||||||
// The method name
|
// The method name
|
||||||
private String method;
|
private String method;
|
||||||
|
|
||||||
|
// Notification Frequency
|
||||||
|
private NotificationFrequency frequency;
|
||||||
|
|
||||||
// Cache of interface proxies (by interface class)
|
// Cache of interface proxies (by interface class)
|
||||||
private Map<Class, Object> proxies = new HashMap<Class, Object>();
|
private Map<Class, Object> proxies = new HashMap<Class, Object>();
|
||||||
|
|
||||||
@@ -58,11 +61,23 @@ public class JavaBehaviour implements Behaviour
|
|||||||
* @param method the method name
|
* @param method the method name
|
||||||
*/
|
*/
|
||||||
public JavaBehaviour(Object instance, String method)
|
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("Instance", instance);
|
||||||
ParameterCheck.mandatory("Method", method);
|
ParameterCheck.mandatory("Method", method);
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
this.frequency = frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -114,6 +129,15 @@ public class JavaBehaviour implements Behaviour
|
|||||||
return stack.search(hashCode()) == -1;
|
return stack.search(hashCode()) == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.alfresco.repo.policy.Behaviour#getNotificationFrequency()
|
||||||
|
*/
|
||||||
|
public NotificationFrequency getNotificationFrequency()
|
||||||
|
{
|
||||||
|
return frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
@@ -30,4 +30,15 @@ public interface Policy
|
|||||||
* derived policies
|
* derived policies
|
||||||
*/
|
*/
|
||||||
static String NAMESPACE = NamespaceService.ALFRESCO_URI;
|
static String NAMESPACE = NamespaceService.ALFRESCO_URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Argument Configuration
|
||||||
|
*/
|
||||||
|
public enum Arg
|
||||||
|
{
|
||||||
|
KEY,
|
||||||
|
START_VALUE,
|
||||||
|
END_VALUE
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.repo.policy.Policy.Arg;
|
||||||
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
import org.alfresco.service.cmr.dictionary.AssociationDefinition;
|
||||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
@@ -47,6 +48,7 @@ public class PolicyComponentImpl implements PolicyComponent
|
|||||||
|
|
||||||
// Policy interface annotations
|
// Policy interface annotations
|
||||||
private static String ANNOTATION_NAMESPACE = "NAMESPACE";
|
private static String ANNOTATION_NAMESPACE = "NAMESPACE";
|
||||||
|
private static String ANNOTATION_ARG ="ARG_";
|
||||||
|
|
||||||
// Dictionary Service
|
// Dictionary Service
|
||||||
private DictionaryService dictionary;
|
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)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.policy.PolicyComponent#registerClassPolicy()
|
* @see org.alfresco.repo.policy.PolicyComponent#registerClassPolicy()
|
||||||
*/
|
*/
|
||||||
@@ -509,8 +522,38 @@ public class PolicyComponentImpl implements PolicyComponent
|
|||||||
}
|
}
|
||||||
String name = methods[0].getName();
|
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
|
// 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 QName policy;
|
||||||
private Class policyIF;
|
private Class policyIF;
|
||||||
|
private Arg[] args;
|
||||||
|
|
||||||
/*package*/ PolicyDefinitionImpl(QName policy, Class policyIF)
|
/*package*/ PolicyDefinitionImpl(QName policy, Class policyIF, Arg[] args)
|
||||||
{
|
{
|
||||||
this.policy = policy;
|
this.policy = policy;
|
||||||
this.policyIF = policyIF;
|
this.policyIF = policyIF;
|
||||||
|
this.args = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
@@ -605,6 +650,27 @@ public class PolicyComponentImpl implements PolicyComponent
|
|||||||
return PolicyType.Association;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.policy;
|
package org.alfresco.repo.policy;
|
||||||
|
|
||||||
|
import org.alfresco.repo.policy.Policy.Arg;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +47,23 @@ public interface PolicyDefinition<P extends Policy>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Policy type
|
* Gets the Policy type
|
||||||
|
*
|
||||||
* @return the policy type
|
* @return the policy type
|
||||||
*/
|
*/
|
||||||
public PolicyType getType();
|
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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,8 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.alfresco.repo.policy.Behaviour.NotificationFrequency;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Policy Factory is responsible for creating Policy implementations.
|
* A Policy Factory is responsible for creating Policy implementations.
|
||||||
@@ -42,6 +44,12 @@ import java.util.List;
|
|||||||
// The policy interface class
|
// The policy interface class
|
||||||
private Class<P> policyClass;
|
private Class<P> policyClass;
|
||||||
|
|
||||||
|
// NOOP Invocation Handler
|
||||||
|
private static InvocationHandler NOOPHandler = new NOOPHandler();
|
||||||
|
|
||||||
|
// Transaction Invocation Handler Factory
|
||||||
|
private static TransactionInvocationHandlerFactory transactionHandlerFactory = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct.
|
* Construct.
|
||||||
@@ -56,6 +64,17 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Transaction Invocation Handler
|
||||||
|
*
|
||||||
|
* @param handlerFactory
|
||||||
|
*/
|
||||||
|
protected static void setTransactionInvocationHandlerFactory(TransactionInvocationHandlerFactory factory)
|
||||||
|
{
|
||||||
|
transactionHandlerFactory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Policy class created by this factory
|
* Gets the Policy class created by this factory
|
||||||
*
|
*
|
||||||
@@ -95,6 +114,16 @@ import java.util.List;
|
|||||||
{
|
{
|
||||||
Behaviour behaviour = behaviourDef.getBehaviour();
|
Behaviour behaviour = behaviourDef.getBehaviour();
|
||||||
P policyIF = behaviour.getInterface(policyClass);
|
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);
|
policyInterfaces.add(policyIF);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +148,7 @@ import java.util.List;
|
|||||||
else if (policyList.size() == 0)
|
else if (policyList.size() == 0)
|
||||||
{
|
{
|
||||||
return (P)Proxy.newProxyInstance(policyClass.getClassLoader(),
|
return (P)Proxy.newProxyInstance(policyClass.getClassLoader(),
|
||||||
new Class[]{policyClass}, new NOOPHandler());
|
new Class[]{policyClass}, NOOPHandler);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -166,8 +195,7 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @param <P> policy interface
|
* @param <P> policy interface
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("hiding")
|
private static class MultiHandler<P extends Policy> implements InvocationHandler, PolicyList
|
||||||
private static class MultiHandler<P> implements InvocationHandler, PolicyList
|
|
||||||
{
|
{
|
||||||
private Collection<P> policyInterfaces;
|
private Collection<P> policyInterfaces;
|
||||||
|
|
||||||
@@ -186,13 +214,13 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
|
||||||
{
|
{
|
||||||
// Handle PolicyList level methods
|
// Handle PolicyList level methods
|
||||||
if (method.getDeclaringClass().equals(PolicyList.class))
|
if (method.getDeclaringClass().equals(PolicyList.class))
|
||||||
{
|
{
|
||||||
return method.invoke(this, args);
|
return method.invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle Object level methods
|
// Handle Object level methods
|
||||||
if (method.getName().equals("toString"))
|
if (method.getName().equals("toString"))
|
||||||
{
|
{
|
||||||
return toString() + ": wrapped " + policyInterfaces.size() + " policies";
|
return toString() + ": wrapped " + policyInterfaces.size() + " policies";
|
||||||
@@ -222,13 +250,13 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.policy.PolicyList#getPolicies()
|
* @see org.alfresco.repo.policy.PolicyList#getPolicies()
|
||||||
*/
|
*/
|
||||||
public Collection getPolicies()
|
public Collection getPolicies()
|
||||||
{
|
{
|
||||||
return policyInterfaces;
|
return policyInterfaces;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user