version upgrade; minor refactor

This commit is contained in:
Brian Long 2024-08-07 18:05:43 -04:00
parent afcfbbc61a
commit dc916ecdd6
37 changed files with 375 additions and 142 deletions

BIN
metadata.keystore Normal file

Binary file not shown.

23
pom.xml
View File

@ -10,6 +10,14 @@
<name>Annotations ACS Platform Module</name>
<description>A module to support annotation-based development for Alfresco Content Services modules.</description>
<url>https://bitbucket.org/inteligr8/annotations-platform-module</url>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.txt</url>
</license>
</licenses>
<scm>
<connection>scm:git:https://bitbucket.org/inteligr8/annotations-platform-module.git</connection>
@ -34,9 +42,10 @@
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<alfresco.sdk.version>4.2.0</alfresco.sdk.version>
<alfresco.platform.version>6.2.0-ga</alfresco.platform.version>
<aspectj.version>1.9.4</aspectj.version>
<alfresco.sdk.version>4.8.0</alfresco.sdk.version>
<alfresco.platform.version>7.4.2</alfresco.platform.version>
<alfresco.platform.war.version>22.22</alfresco.platform.war.version>
<aspectj.version>1.9.19</aspectj.version>
<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts>
</properties>
@ -63,7 +72,7 @@
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aspectj-platform-module</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0.0</version>
</dependency>
</dependencies>
@ -77,11 +86,11 @@
<configuration>
<tiles>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile -->
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.0.0,1.1.0)</tile>
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0)</tile>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.0.0,1.1.0)</tile>
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0)</tile>
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile -->
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.0.0,1.1.0)</tile>
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0)</tile>
</tiles>
</configuration>
</plugin>

2
rad.sh
View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh
discoverArtifactId() {
ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'`

View File

@ -5,10 +5,20 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to execute the annotated method
* asynchronously. The execution may be performed any number of ways,
* including a threaded execution or through a queuing service.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Asynchronous {
/**
* Whether or not the execution is guaranteed.
*
* @return `true` if guaranteed; `false` otherwise
*/
boolean durable() default true;
}

View File

@ -1,7 +1,15 @@
package com.inteligr8.alfresco.annotations;
/**
* This interface provides a way to specify a user for expected authorizations.
*
* @see com.inteligr8.alfresco.annotations.Authorized
*/
public interface Authorizable {
/**
* @return An ACS user ID.
*/
String authorizeAsUser();
}

View File

@ -5,10 +5,33 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to execute the annotated method
* inside an ACS authorized context. This is how the execution can be elevated
* to a service account or de-escalated to a user account.
*
* If the authorization is expected to be the same (the same user), then
* another layer of authorization is **not** added.
*
* Use the Authorizable interface to provide a dynamic user ID for the
* authorization context.
*
* @see com.inteligr8.alfresco.annotations.Authorizable
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Authorized {
/**
* The user ID to use for the authorization context.
*
* It is important to note that if the Authorizable interface is
* implemented, then the value returned from its method will take
* precedence over this one. This capability is useful for to support
* dynamic user authorization contexts.
*
* @return An ACS user ID; empty will be treated as `system`.
*/
String value() default "";
}

View File

@ -5,6 +5,14 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to execute the annotated method
* inside an ACS `system` authorized context. This is the highest privileged
* execution.
*
* If the authorization is expected to be the same (remains `system`), then
* another layer of authorization is **not** added.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthorizedAsSystem {

View File

@ -5,6 +5,11 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to skip execution if the annotated
* parameter or any annotated method parameter is a child association that is
* not primary.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,

View File

@ -5,6 +5,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to skip execution if the annotated
* parameter or any annotated method parameter is a node reference and it does
* not exist. Unless cached, this will result in the framework consulting with
* the database.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,

View File

@ -5,6 +5,15 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to skip execution if the annotated
* parameter or any annotated method parameter is a node reference and it does
* not have the specified aspect. Unless cached, this will result in the
* framework consulting with the database.
*
* This includes support for checking the child node reference of a
* parent-child association and both the source/target of a peer association.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,
@ -12,6 +21,9 @@ import java.lang.annotation.Target;
})
public @interface IfNodeHasAspect {
/**
* @return An ACS aspect in the Alfresco QName prefixed format (e.g. `cm:auditable`).
*/
String aspect() default "";
}

View File

@ -5,6 +5,15 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to skip execution if the annotated
* parameter or any annotated method parameter is a node reference and it is
* not of the specified type. Unless cached, this will result in the framework
* consulting with the database.
*
* This includes support for checking the child node reference of a
* parent-child association and both the source/target of a peer association.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,
@ -12,6 +21,9 @@ import java.lang.annotation.Target;
})
public @interface IfNodeOfType {
/**
* @return An ACS node type in the Alfresco QName prefixed format (e.g. `cm:content`).
*/
String type() default "";
}

View File

@ -5,6 +5,10 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to skip execution if the annotated
* parameter or any annotated method parameter is `null`.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD,

View File

@ -7,7 +7,7 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ClusterSynchronized {
public @interface JobSynchronized {
String value() default "";

View File

@ -5,20 +5,48 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* This annotation tells the framework to execute the annotated method inside a
* pool of threads.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Threaded {
/**
* @return A name for the thread pool.
*/
String name() default "";
/**
* @return A number of threads to execute.
*/
int threads() default 1;
/**
* @return A maximum number of threads to execute at any one time; the thread pool size.
*/
int concurrency() default 0;
/**
* @return A Java thread priority for all the threads.
*/
int priority() default Thread.NORM_PRIORITY;
/**
* Whether or not the calling thread should wait for all the threads to complete.
*
* @return `true` to wait; `false` to return immediately.
*/
boolean join() default false;
/**
* How long the calling thread should wait before returning. If a timeout
* is reached, a TimeoutException will be thrown. If this is not desired,
* then `join()` should return `false`.
*
* @return A number of milliseconds to wait; 0 waits indefinitely
*/
long joinWaitMillis() default 0L;
}

View File

@ -5,16 +5,41 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
/**
* This annotation tells the framework to wrap the annotated method inside an
* ACS API retryable transaction. This may be used in conjunction with the
* Spring Transactional annotation.
*
* @see org.springframework.transaction.annotation.Transactional
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TransactionalRetryable {
/**
* @return A number of retries; -1 for ACS API default (unlimited).
* @see RetryingTransactionHelper#setMaxRetries(int)
*/
int maxRetries() default -1;
/**
* @return A minimum number of milliseconds to wait between retries; -1 for ACS API default (200 ms).
* @see RetryingTransactionHelper#setMinRetryWaitMs(int)
*/
int minRetryWaitInMillis() default -1;
/**
* @return A maximum number of milliseconds to wait between retries; -1 for ACS API default (2000 ms).
* @see RetryingTransactionHelper#setMaxRetryWaitMs(int)
*/
int maxRetryWaitInMillis() default -1;
/**
* @return A number of milliseconds to progressively add to the wait after each attempt; -1 for ACS API default (100 ms).
* @see RetryingTransactionHelper#setRetryWaitIncrementMs(int)
*/
int incRetryWaitInMillis() default -1;
}

View File

@ -2,11 +2,21 @@ package com.inteligr8.alfresco.annotations.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AbstractMethodAspect<A extends Annotation> extends AbstractWarnOnceService {
public abstract class AbstractMethodAspect<A extends Annotation> extends AbstractWarnOnceService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private Set<Pair<String, String>> warned = new HashSet<>();
protected A getAnnotation(ProceedingJoinPoint joinPoint, Class<A> annotationClass, boolean warnReturn, boolean warnThrows) {
Method method = this.getMethod(joinPoint, annotationClass, warnReturn, warnThrows);
@ -49,5 +59,26 @@ public class AbstractMethodAspect<A extends Annotation> extends AbstractWarnOnce
return obj.toString();
}
}
protected boolean validate(ProceedingJoinPoint joinPoint, Class<A> annotationClass,
Collection<Class<? extends Annotation>> ignoredAnnotationClasses, Collection<Class<? extends Annotation>> disallowedAnnotationClasses) {
Method method = this.getMethod(joinPoint, annotationClass, false, false);
for (Class<? extends Annotation> a : ignoredAnnotationClasses) {
if (method.isAnnotationPresent(a)) {
if (this.warned.add(Pair.of(a.getName(), annotationClass.getName())))
this.logger.warn("@{} cannot be used on the same method as @{}; ignoring annotation", a.getSimpleName(), annotationClass.getSimpleName());
}
}
for (Class<? extends Annotation> a : disallowedAnnotationClasses) {
if (method.isAnnotationPresent(a)) {
this.logger.error("@{} cannot be used on the same method as @{}", a.getSimpleName(), annotationClass.getSimpleName());
return false;
}
}
return true;
}
}

View File

@ -6,7 +6,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AbstractWarnOnceService {
public abstract class AbstractWarnOnceService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -14,10 +14,13 @@ import org.springframework.beans.factory.annotation.Qualifier;
import com.inteligr8.alfresco.annotations.Asynchronous;
import com.inteligr8.alfresco.annotations.service.AsyncService;
/**
* This aspect implements the @Asynchronous annotation.
*
* @see com.inteligr8.alfresco.annotations.Asynchronous
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AsyncAspect, *")
//@Order(Ordered.HIGHEST_PRECEDENCE + Byte.MAX_VALUE)
//@Component
public class AsyncAspect extends AbstractMethodAspect<Asynchronous> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -18,10 +18,14 @@ import com.inteligr8.alfresco.annotations.Authorizable;
import com.inteligr8.alfresco.annotations.Authorized;
import com.inteligr8.alfresco.annotations.AuthorizedAsSystem;
/**
* This aspect implements the Authorized and AuthorizedAsSystem annotations.
*
* @see com.inteligr8.alfresco.annotations.Authorized
* @see com.inteligr8.alfresco.annotations.AuthorizedAsSystem
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
//@Order(Ordered.HIGHEST_PRECEDENCE + Short.MAX_VALUE)
//@Component
public class AuthorizedAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -73,7 +77,7 @@ public class AuthorizedAspect {
Authorized runAsAnnotation = method.getAnnotation(Authorized.class);
String runAs = StringUtils.trimToNull(runAsAnnotation.value());
if (runAs != null) {
if (runAs != null && runAs.length() > 0) {
this.logger.trace("The @Authorized method '{}' must run as: {}", method, runAs);
return runAs;
}

View File

@ -12,8 +12,12 @@ import org.slf4j.LoggerFactory;
import com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary;
/**
* This aspect implements the IfChildAssociationIsPrimary annotation.
*
* @see com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary
*/
@Aspect
//@Component
public class ChildIsPrimaryAspect extends AbstractMethodOrParameterAspect<IfChildAssociationIsPrimary> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -14,13 +14,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.inteligr8.alfresco.annotations.ClusterSynchronized;
import com.inteligr8.alfresco.annotations.JobSynchronized;
/**
* This aspect implements the JobSynchronized annotation.
*
* @see com.inteligr8.alfresco.annotations.JobSynchronized
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect, com.inteligr8.alfresco.annotations.aspect.ClusterSynchronizedAspect")
//@Component
//@Order(Ordered.LOWEST_PRECEDENCE - Byte.MAX_VALUE)
public class ClusterSynchronizedAspect extends AbstractMethodAspect<ClusterSynchronized> {
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect, com.inteligr8.alfresco.annotations.aspect.JobLockAspect")
public class JobLockAspect extends AbstractMethodAspect<JobSynchronized> {
private static final String NS = "http://inteligr8.com/alfresco/model";
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -28,33 +31,33 @@ public class ClusterSynchronizedAspect extends AbstractMethodAspect<ClusterSynch
@Autowired
private JobLockService jobLockService;
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.ClusterSynchronized) && execution(* *(..))")
public void isClusterSyncAnnotated() {
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.JobSynchronized) && execution(* *(..))")
public void isJobSyncAnnotated() {
}
@Around("isClusterSyncAnnotated()")
public Object clusterSync(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("clusterSync({})", joinPoint);
@Around("isJobSyncAnnotated()")
public Object jobSync(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("jobSync({})", joinPoint);
Method method = this.getMethod(joinPoint, ClusterSynchronized.class, false, false);
ClusterSynchronized clusterSync = method.getAnnotation(ClusterSynchronized.class);
Method method = this.getMethod(joinPoint, JobSynchronized.class, false, false);
JobSynchronized clusterSync = method.getAnnotation(JobSynchronized.class);
QName lockQName = this.getLockQName(clusterSync, method);
this.logger.debug("Acquiring cluster lock: {}", lockQName);
this.logger.debug("Acquiring job lock: {}", lockQName);
String lockToken = this.jobLockService.getLock(lockQName, clusterSync.lockTimeoutInMillis(),
clusterSync.acquireWaitBetweenRetriesInMillis(), clusterSync.acquireMaxRetries());
try {
this.logger.trace("Acquired cluster lock: {}", lockQName);
this.logger.trace("Acquired job lock: {}", lockQName);
return joinPoint.proceed();
} finally {
this.logger.debug("Releasing cluster lock: {}", lockQName);
this.logger.debug("Releasing job lock: {}", lockQName);
this.jobLockService.releaseLock(lockToken, lockQName);
}
}
private QName getLockQName(ClusterSynchronized clusterSync, Method method) {
private QName getLockQName(JobSynchronized clusterSync, Method method) {
String lockName = StringUtils.trimToNull(clusterSync.value());
if (lockName != null) {
return QName.createQNameWithValidLocalName(NS, lockName);

View File

@ -1,16 +1,12 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
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.NodeService;
import org.alfresco.service.namespace.QName;
@ -25,12 +21,16 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import com.inteligr8.alfresco.annotations.NodeAspectConstrainable;
import com.inteligr8.alfresco.annotations.IfNodeHasAspect;
import com.inteligr8.alfresco.annotations.NodeAspectConstrainable;
/**
* This aspect implements the IfNodeHasAspect annotation.
*
* @see com.inteligr8.alfresco.annotations.IfNodeHasAspect
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect, com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect")
//@Component
public class NodeAspectAspect extends QNameBasedAspect<IfNodeHasAspect> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -128,36 +128,5 @@ public class NodeAspectAspect extends QNameBasedAspect<IfNodeHasAspect> {
}
});
}
private Collection<NodeRef> extractNodeRefs(Object obj) {
if (obj instanceof NodeRef) {
NodeRef nodeRef = (NodeRef) obj;
return Collections.singleton(nodeRef);
} else if (obj instanceof ChildAssociationRef) {
ChildAssociationRef childAssocRef = (ChildAssociationRef) obj;
return Collections.singleton(childAssocRef.getChildRef());
} else if (obj instanceof AssociationRef) {
AssociationRef assocRef = (AssociationRef) obj;
return Arrays.asList(assocRef.getSourceRef(), assocRef.getTargetRef());
} else if (obj instanceof Collection<?>) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Collection<?>) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else if (obj instanceof Object[]) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Object[]) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else {
return null;
}
}
}

View File

@ -1,16 +1,11 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
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.NodeService;
import org.alfresco.service.namespace.QName;
@ -28,9 +23,13 @@ import org.springframework.beans.factory.annotation.Value;
import com.inteligr8.alfresco.annotations.IfNodeOfType;
import com.inteligr8.alfresco.annotations.NodeTypeConstrainable;
/**
* This aspect implements the IfNodeOfType annotation.
*
* @see com.inteligr8.alfresco.annotations.IfNodeOfType
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect, com.inteligr8.alfresco.annotations.aspect.NodeTypeAspect")
//@Component
public class NodeTypeAspect extends QNameBasedAspect<IfNodeOfType> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -128,36 +127,5 @@ public class NodeTypeAspect extends QNameBasedAspect<IfNodeOfType> {
}
});
}
private Collection<NodeRef> extractNodeRefs(Object obj) {
if (obj instanceof NodeRef) {
NodeRef nodeRef = (NodeRef) obj;
return Collections.singleton(nodeRef);
} else if (obj instanceof ChildAssociationRef) {
ChildAssociationRef childAssocRef = (ChildAssociationRef) obj;
return Collections.singleton(childAssocRef.getChildRef());
} else if (obj instanceof AssociationRef) {
AssociationRef assocRef = (AssociationRef) obj;
return Arrays.asList(assocRef.getSourceRef(), assocRef.getTargetRef());
} else if (obj instanceof Collection<?>) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Collection<?>) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else if (obj instanceof Object[]) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Object[]) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else {
return null;
}
}
}

View File

@ -12,9 +12,12 @@ import org.slf4j.LoggerFactory;
import com.inteligr8.alfresco.annotations.IfNotNull;
/**
* This aspect implements the IfNotNull annotation.
*
* @see com.inteligr8.alfresco.annotations.IfNotNull
*/
@Aspect
//@Order(Ordered.HIGHEST_PRECEDENCE + 64)
//@Component
public class NotNullAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -22,9 +22,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import com.inteligr8.alfresco.annotations.IfNodeExists;
/**
* This aspect implements the IfNodeExists annotation.
*
* @see com.inteligr8.alfresco.annotations.IfNodeExists
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect, com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect")
//@Component
public class OperableNodeAspect extends AbstractMethodOrParameterAspect<IfNodeExists> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -1,14 +1,20 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.PostConstruct;
import org.alfresco.repo.cache.DefaultSimpleCache;
import org.alfresco.repo.cache.SimpleCache;
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.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
@ -76,6 +82,37 @@ public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMet
}
}
protected Collection<NodeRef> extractNodeRefs(Object obj) {
if (obj instanceof NodeRef) {
NodeRef nodeRef = (NodeRef) obj;
return Collections.singleton(nodeRef);
} else if (obj instanceof ChildAssociationRef) {
ChildAssociationRef childAssocRef = (ChildAssociationRef) obj;
return Collections.singleton(childAssocRef.getChildRef());
} else if (obj instanceof AssociationRef) {
AssociationRef assocRef = (AssociationRef) obj;
return Arrays.asList(assocRef.getSourceRef(), assocRef.getTargetRef());
} else if (obj instanceof Collection<?>) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Collection<?>) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else if (obj instanceof Object[]) {
Set<NodeRef> nodeRefs = new LinkedHashSet<>();
for (Object o : ((Object[]) obj)) {
Collection<NodeRef> subNodeRefs = this.extractNodeRefs(o);
if (subNodeRefs != null)
nodeRefs.addAll(subNodeRefs);
}
return nodeRefs;
} else {
return null;
}
}
public interface QNameBasedCallback<T> {

View File

@ -21,10 +21,24 @@ import org.springframework.transaction.annotation.Transactional;
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
/**
* This aspect implements the @Transactional and @TransactionalRetryable
* annotations.
*
* Most notably, it implements the Spring @Transactional annotation, so it
* works when used within ACS modules. Both could be used; or just either one.
* Each situation has a different meaning.
*
* - Without @TransactionalRetryable, it will not automatically retry due to
* expected concurrency issues.
* - Without @Transactional, it will be like a readonly
* @Transactional(SUPPORTS)
*
* @see org.springframework.transaction.annotation.Transactional
* @see com.inteligr8.alfresco.annotations.TransactionalRetryable
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
//@Component
//@Order(Ordered.LOWEST_PRECEDENCE - Short.MAX_VALUE)
public class RetryingTransactionAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

View File

@ -9,6 +9,7 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import org.aspectj.lang.ProceedingJoinPoint;
@ -24,10 +25,13 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.inteligr8.alfresco.annotations.Threadable;
import com.inteligr8.alfresco.annotations.Threaded;
/**
* This aspect implements the @Threaded annotation.
*
* @see com.inteligr8.alfresco.annotations.Threaded
*/
@Aspect
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AsyncAspect, com.inteligr8.alfresco.annotations.aspect.ThreadedAspect, *")
//@Order(Ordered.HIGHEST_PRECEDENCE + Byte.MAX_VALUE)
//@Component
public class ThreadedAspect extends AbstractMethodAspect<Threaded> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -53,6 +57,7 @@ public class ThreadedAspect extends AbstractMethodAspect<Threaded> {
this.logger.trace("threaded({})", joinPoint);
Threaded threaded = this.getAnnotation(joinPoint, Threaded.class, true, true);
MergedThreadConfiguration threadConfig = new MergedThreadConfiguration(joinPoint, threaded);
ThreadFactoryBuilder tfbuilder = new ThreadFactoryBuilder()
@ -86,13 +91,16 @@ public class ThreadedAspect extends AbstractMethodAspect<Threaded> {
if (threaded.join()) {
long waitMillis = threaded.joinWaitMillis() == 0L ? 300000L : threaded.joinWaitMillis();
do {
while (true) {
this.logger.debug("Blocking this thread until subthreads finish: {}", Thread.currentThread().getId());
if (threadExecutor.awaitTermination(waitMillis, TimeUnit.MILLISECONDS)) {
if (!threadExecutor.awaitTermination(waitMillis, TimeUnit.MILLISECONDS)) {
if (threaded.joinWaitMillis() > 0L)
throw new TimeoutException();
} else {
this.logger.trace("Subthreads finished; unblocking this thread: {}", Thread.currentThread().getId());
break;
}
} while (threaded.joinWaitMillis() == 0L);
}
this.logger.debug("Subthreads running: {}; unblocking this thread: {}", threadExecutor.getActiveCount(), Thread.currentThread().getId());
}

View File

@ -17,6 +17,9 @@ import org.alfresco.service.transaction.TransactionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* This bean implements a standard TransactionManager for ACS.
*/
@Component
public class AlfrescoTransactionManager implements TransactionManager {

View File

@ -30,7 +30,6 @@ import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.transaction.TransactionManager;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache;
@ -48,8 +47,8 @@ import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.Pair;
import org.apache.activemq.ActiveMQXAConnectionFactory;
import org.apache.activemq.jms.pool.XaPooledConnectionFactory;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.jms.pool.PooledConnectionFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.quartz.JobKey;
@ -79,7 +78,7 @@ import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
import com.inteligr8.alfresco.annotations.service.AsyncService;
/**
* This class provides integration with MQ for the asynchronous filing of nodes.
* This class provides integration with MQ for the asynchronous method executions.
*
* @author brian@inteligr8.com
*/
@ -98,7 +97,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic
protected int workerThreads;
@Value("${inteligr8.async.mq.url}")
//@Value("${messaging.broker.url}")
protected String url;
@Value("${inteligr8.async.mq.username}")
@ -137,12 +135,9 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic
@Autowired
protected TransactionService txService;
@Autowired
protected TransactionManager txManager;
private String hostname;
private XaPooledConnectionFactory factory;
private PooledConnectionFactory factory;
private SimpleCache<Pair<Class<?>, String>, Method> methodCache;
@ -164,12 +159,11 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic
this.hostname = "unknown";
}
ActiveMQXAConnectionFactory factory = new ActiveMQXAConnectionFactory(this.url);
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url);
XaPooledConnectionFactory pool = new XaPooledConnectionFactory();
PooledConnectionFactory pool = new PooledConnectionFactory();
pool.setConnectionFactory(factory);
pool.setMaxConnections(this.maxConnections);
pool.setTransactionManager(this.txManager);
pool.start();
this.factory = pool;

View File

@ -33,7 +33,8 @@ import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
import com.inteligr8.alfresco.annotations.service.AsyncService;
/**
* This class provides a non-persistent alternative to MQ for asynchronous execution.
* This class provides a non-persistent alternative to MQ for asynchronous method
* execution.
*
* @author brian@inteligr8.com
*/

View File

@ -7,7 +7,7 @@
<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.ClusterSynchronizedAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.JobLockAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeTypeAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect" />

View File

@ -0,0 +1,2 @@
logger.inteligr8-annotations.name=com.inteligr8.alfresco.annotations
logger.inteligr8-annotations.level=info

View File

@ -1,4 +1,9 @@
module.id=${project.artifactId}
module.id=${project.groupId}.${project.artifactId}
module.title=${project.name}
module.description=${project.description}
module.version=${project.version}
module.repo.version.min=6.0
#module.repo.version.max=
module.depends.aspectj-platform-module=1.0-*

View File

@ -6,37 +6,52 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
@Component
public class ClusterSynchronizedTest extends AbstractLifecycleBean {
public class JobSynchronizedTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
MutableInt threadsRun = new MutableInt();
this.simpleLock();
this.threadThenLock(threadsRun);
this.threadAndLock(threadsRun);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Threaded(name = "cluster-sync", threads = 5, join = true)
@Transactional
@ClusterSynchronized
private void threadThenLock(MutableInt threadsRun) {
@JobSynchronized
private void simpleLock() {
this.logger.debug("simpleLock()");
}
@Threaded(name = "job-sync", threads = 5, join = true)
@JobSynchronized
private void threadAndLock(MutableInt threadsRun) {
this.lock(threadsRun);
}
@Threaded(name = "job-sync", threads = 5, join = true)
private void threadThenLock(MutableInt threadsRun) {
this.lock(threadsRun);
}
@JobSynchronized
private void lock(MutableInt threadsRun) {
this.locked(threadsRun);
}
private void locked(MutableInt threadsRun) {
int t = threadsRun.intValue();
this.logger.debug("After start of a mutually exclusive execution block: {}", t);
try {
Thread.sleep(200L);
Thread.sleep(100L);
} catch (InterruptedException ie) {
}

View File

@ -6,3 +6,6 @@ log4j.logger.org.springframework.extensions.webscripts.ScriptLogger=debug
# non-WebScript JavaScript execution debugging
log4j.logger.org.alfresco.repo.jscript.ScriptLogger=debug
# Module importing
#log4j.logger.org.alfresco.repo.module.ImporterModuleComponent=trace

View File

@ -0,0 +1,12 @@
# Module debugging
logger.inteligr8-annotations.level=trace
# WebScript debugging
logger.springframework-extensions-webscripts-ScriptLogger.level=debug
# non-WebScript JavaScript execution debugging
logger.alfresco-repo-jscript-ScriptLogger.level=debug
# Module importing
#logger.alfresco-repo-module-importer.name=org.alfresco.repo.module.ImporterModuleComponent
#logger.alfresco-repo-module-importer.level=trace