refactored

This commit is contained in:
2024-09-20 17:11:06 -04:00
parent 62da2b3830
commit 38b9f6e35f
91 changed files with 1305 additions and 651 deletions
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>annotations-platform-module</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>annotations-core-platform-module</artifactId>
<packaging>jar</packaging>
<properties>
<alfresco.platform.war.version>22.22</alfresco.platform.war.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.40</version>
<extensions>true</extensions>
<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.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.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.1.0,1.2.0)</tile>
</tiles>
</configuration>
</plugin>
</plugins>
</build>
</project>
+74
View File
@@ -0,0 +1,74 @@
function discoverArtifactId {
$script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate)
}
function rebuild {
echo "Rebuilding project ..."
mvn process-classes
}
function start_ {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad process-classes
}
function start_log {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad "-Ddocker.showLogs" process-classes
}
function stop_ {
discoverArtifactId
echo "Stopping Docker containers that supported rapid application development ..."
docker container ls --filter name=${ARTIFACT_ID}-*
echo "Stopping containers ..."
docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*)
echo "Removing containers ..."
docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*)
}
function tail_logs {
param (
$container
)
discoverArtifactId
docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container})
}
function list {
discoverArtifactId
docker container ls --filter name=${ARTIFACT_ID}-*
}
switch ($args[0]) {
"start" {
start_
}
"start_log" {
start_log
}
"stop" {
stop_
}
"restart" {
stop_
start_
}
"rebuild" {
rebuild
}
"tail" {
tail_logs $args[1]
}
"containers" {
list
}
default {
echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
}
}
echo "Completed!"
+71
View File
@@ -0,0 +1,71 @@
#!/bin/sh
discoverArtifactId() {
ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'`
}
rebuild() {
echo "Rebuilding project ..."
mvn process-test-classes
}
start() {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad process-test-classes
}
start_log() {
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
mvn -Drad -Ddocker.showLogs process-test-classes
}
stop() {
discoverArtifactId
echo "Stopping Docker containers that supported rapid application development ..."
docker container ls --filter name=${ARTIFACT_ID}-*
echo "Stopping containers ..."
docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*`
echo "Removing containers ..."
docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*`
}
tail_logs() {
discoverArtifactId
docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1`
}
list() {
discoverArtifactId
docker container ls --filter name=${ARTIFACT_ID}-*
}
case "$1" in
start)
start
;;
start_log)
start_log
;;
stop)
stop
;;
restart)
stop
start
;;
rebuild)
rebuild
;;
tail)
tail_logs $2
;;
containers)
list
;;
*)
echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
exit 1
esac
echo "Completed!"
@@ -0,0 +1,11 @@
package com.inteligr8.alfresco.annotations;
import java.util.Collection;
import org.alfresco.service.namespace.QNamePattern;
public interface AssociationTypeConstrainable {
Collection<? extends QNamePattern> constrainedNodeTypes();
}
@@ -0,0 +1,24 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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;
}
@@ -0,0 +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();
}
@@ -0,0 +1,37 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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 "";
}
@@ -0,0 +1,20 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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 {
}
@@ -0,0 +1,20 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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,
ElementType.PARAMETER
})
public @interface IfChildAssociationIsPrimary {
}
@@ -0,0 +1,21 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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,
ElementType.PARAMETER
})
public @interface IfNodeExists {
}
@@ -0,0 +1,29 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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,
ElementType.PARAMETER
})
public @interface IfNodeHasAspect {
/**
* @return An ACS aspect in the Alfresco QName prefixed format (e.g. `cm:auditable`).
*/
String aspect() default "";
}
@@ -0,0 +1,29 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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,
ElementType.PARAMETER
})
public @interface IfNodeOfType {
/**
* @return An ACS node type in the Alfresco QName prefixed format (e.g. `cm:content`).
*/
String type() default "";
}
@@ -0,0 +1,19 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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,
ElementType.PARAMETER
})
public @interface IfNotNull {
}
@@ -0,0 +1,20 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JobSynchronized {
String value() default "";
long acquireWaitBetweenRetriesInMillis() default 100L;
int acquireMaxRetries() default 300;
long lockTimeoutInMillis() default 5000L;
}
@@ -0,0 +1,42 @@
package com.inteligr8.alfresco.annotations;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.springframework.beans.factory.BeanNameAware;
public interface NodeAspectConstrainable extends BeanNameAware {
String getBeanName();
NamespaceService getNamespaceService();
default Collection<String> constrainedPrefixedAspects() {
return Collections.emptySet();
}
default Collection<String> constrainedRegexedAspects() {
return Collections.emptySet();
}
default Collection<? extends QNamePattern> constrainedAspects() {
Set<QNamePattern> aspects = new HashSet<>();
Collection<String> prefixedAspects = this.constrainedPrefixedAspects();
for (String prefixedAspect : prefixedAspects)
aspects.add(QName.createQName(prefixedAspect, this.getNamespaceService()));
Collection<String> regexedAspects = this.constrainedRegexedAspects();
for (String regexedAspect : regexedAspects)
aspects.add(new RegexQNamePattern(regexedAspect));
return aspects;
}
}
@@ -0,0 +1,42 @@
package com.inteligr8.alfresco.annotations;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.springframework.beans.factory.BeanNameAware;
public interface NodeTypeConstrainable extends BeanNameAware {
String getBeanName();
NamespaceService getNamespaceService();
default Collection<String> constrainedPrefixedNodeTypes() {
return Collections.emptySet();
}
default Collection<String> constrainedRegexedNodeTypes() {
return Collections.emptySet();
}
default Collection<? extends QNamePattern> constrainedNodeTypes() {
Set<QNamePattern> nodeTypes = new HashSet<>();
Collection<String> prefixedNodeTypes = this.constrainedPrefixedNodeTypes();
for (String prefixedNodeType : prefixedNodeTypes)
nodeTypes.add(QName.createQName(prefixedNodeType, this.getNamespaceService()));
Collection<String> regexedNodeTypes = this.constrainedRegexedNodeTypes();
for (String regexedNodeType : regexedNodeTypes)
nodeTypes.add(new RegexQNamePattern(regexedNodeType));
return nodeTypes;
}
}
@@ -0,0 +1,17 @@
package com.inteligr8.alfresco.annotations;
public interface Threadable {
default Integer getThreads() {
return null;
}
default Integer getConcurrency() {
return null;
}
default Integer getThreadPriority() {
return null;
}
}
@@ -0,0 +1,52 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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;
}
@@ -0,0 +1,45 @@
package com.inteligr8.alfresco.annotations;
import java.lang.annotation.ElementType;
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;
}
@@ -0,0 +1,84 @@
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 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);
return method.getAnnotation(annotationClass);
}
protected Method getMethod(ProceedingJoinPoint joinPoint, Object messagePrefix, boolean warnReturn, boolean warnThrows) {
if (!(joinPoint.getSignature() instanceof MethodSignature))
throw new IllegalStateException(this.createMessagePrefix(messagePrefix) + " must be on methods");
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
if (warnReturn && methodSig.getReturnType() != null && !this.isReturnTypeVoid(methodSig)) {
this.warn(joinPoint.toLongString(),
"{} has a return value or throws clause; 'null' is always returned and subthread exceptions don't propagate: {}: {}",
this.createMessagePrefix(messagePrefix), joinPoint, methodSig.getReturnType());
}
if (warnThrows && methodSig.getExceptionTypes() != null && methodSig.getExceptionTypes().length > 0) {
this.warn(joinPoint.toLongString(),
"{} has a return value or throws clause; 'null' is always returned and subthread exceptions don't propagate: {}: {}",
this.createMessagePrefix(messagePrefix), joinPoint, methodSig.getExceptionTypes());
}
return methodSig.getMethod();
}
protected boolean isReturnTypeVoid(MethodSignature methodSig) {
return Void.class.equals(methodSig.getReturnType()) || void.class.equals(methodSig.getReturnType());
}
private String createMessagePrefix(Object obj) {
if (obj instanceof Class<?>) {
Class<?> clazz = (Class<?>) obj;
if (clazz.isAnnotation()) {
return "The @" + ((Class<?>) obj).getSimpleName() + " annotated method";
} else {
return "The " + ((Class<?>) obj).getSimpleName() + " class method";
}
} else {
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;
}
}
@@ -0,0 +1,37 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractMethodOrParameterAspect<T extends Annotation> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public abstract Class<T> getAnnotationClass();
public Object checkParameters(ProceedingJoinPoint joinPoint, ApplicableParameterCallback<T> callback) throws Throwable {
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
T methodAnnotation = method.getAnnotation(this.getAnnotationClass());
for (int p = 0; p < method.getParameterCount(); p++) {
T annotation = method.getParameters()[p].getAnnotation(this.getAnnotationClass());
if (annotation == null)
annotation = methodAnnotation;
if (annotation != null) {
if (!callback.checkParameter(joinPoint, method, annotation, p)) {
this.logger.debug("The parameter '{}' condition is false; skipping method: {}", method.getParameters()[p].getName(), method);
return null;
}
}
}
return joinPoint.proceed();
}
}
@@ -0,0 +1,245 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.annotation.Transactional;
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
import com.inteligr8.alfresco.annotations.util.JtaTransactionalAnnotationAdapter;
import com.inteligr8.alfresco.annotations.util.SpringTransactionalAnnotationAdapter;
import com.inteligr8.alfresco.annotations.util.TransactionalAnnotationAdapter;
/**
* 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
*/
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
public abstract class AbstractRetryingTransactionAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ApplicationContext context;
@Autowired
private TransactionService txService;
public abstract String getJtaInterfaceName();
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))")
public void isTransactionalAnnotated() {
}
public abstract void isJtaTransactionalAnnotated();
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))")
public void isTransactionalRetryableAnnotated() {
}
@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()")
public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("retryingTransactional({})", joinPoint);
Method method = this.getMethod(joinPoint);
TransactionalAnnotationAdapter txl = this.wrapTransactionalAnnotation(method);
TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class);
if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) {
this.logger.debug("Changing TX context: {} => [ro: {}, new: {}]", AlfrescoTransactionSupport.getTransactionReadState(), txl.isReadOnly(), txl.getPropagation());
return this.execute(joinPoint, txl, txtry);
} else if (this.doCreateNewTxRetryContext(txtry)) {
this.logger.debug("Changing TX context: retries: {}", txtry.maxRetries());
return this.execute(joinPoint, null, txtry);
} else {
return joinPoint.proceed();
}
}
private TransactionalAnnotationAdapter wrapTransactionalAnnotation(Method method) {
Annotation txl = method.getAnnotation(Transactional.class);
if (txl != null)
return new SpringTransactionalAnnotationAdapter((Transactional) txl);
txl = this.getOptionalAnnotation(method, this.getJtaInterfaceName());
if (txl != null)
return this.context.getAutowireCapableBeanFactory().getBean(JtaTransactionalAnnotationAdapter.class, txl);
return null;
}
private <A extends Annotation> A getOptionalAnnotation(Method method, String fullyQualifiedAnnotationName) {
try {
@SuppressWarnings("unchecked")
Class<A> annotationClass = (Class<A>) Class.forName(fullyQualifiedAnnotationName);
return method.getAnnotation(annotationClass);
} catch (ClassNotFoundException cnfe) {
this.logger.trace("The {} annotation is not available in the classpath; assuming not set", fullyQualifiedAnnotationName);
return null;
}
}
private Method getMethod(ProceedingJoinPoint joinPoint) {
if (!(joinPoint.getSignature() instanceof MethodSignature))
throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods");
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
return methodSig.getMethod();
}
private boolean isReadStateChange(TransactionalAnnotationAdapter txl) {
if (txl == null)
return false;
switch (txl.getPropagation()) {
case NEVER:
case NOT_SUPPORTED:
case SUPPORTS:
// do not force because of a read-state change
return false;
default:
}
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
case TXN_NONE:
return true;
case TXN_READ_ONLY:
return !txl.isReadOnly();
case TXN_READ_WRITE:
return txl.isReadOnly();
default:
throw new IllegalStateException();
}
}
private boolean doCreateNewTxRetryContext(TransactionalRetryable txtry) {
return txtry != null;
}
private boolean doCreateNewTxContext(TransactionalAnnotationAdapter txl) {
if (txl == null) {
return false;
} else switch (txl.getPropagation()) {
case NEVER:
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
case TXN_NONE:
return false;
default:
throw new IllegalTransactionStateException("A transaction exists where one is not allowed");
}
case MANDATORY:
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
case TXN_NONE:
throw new IllegalTransactionStateException("A transaction does not exist where one is mandatory");
case TXN_READ_ONLY:
if (!txl.isReadOnly())
throw new IllegalTransactionStateException("A read-only transaction exists where a read/write one is mandatory");
case TXN_READ_WRITE:
if (txl.isReadOnly())
throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory");
}
return false;
case NOT_SUPPORTED:
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
case TXN_NONE:
return false;
default:
throw new IllegalTransactionStateException("A transaction exists and pausing it is not supported");
}
case SUPPORTS:
return false;
case REQUIRED:
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
case TXN_NONE:
return true;
default:
return false;
}
case REQUIRES_NEW:
return true;
default:
throw new IllegalTransactionStateException("The transactional propagation is not supported: " + txl.getPropagation());
}
}
private Object execute(final ProceedingJoinPoint joinPoint, TransactionalAnnotationAdapter txl, TransactionalRetryable txtry) throws Throwable {
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable {
logger.debug("entering tx: {}", AlfrescoTransactionSupport.getTransactionId());
try {
return joinPoint.proceed();
} catch (Exception | Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException("This should never happen", t);
} finally {
logger.trace("leaving tx: {}", AlfrescoTransactionSupport.getTransactionId());
}
}
};
RetryingTransactionHelper rthelper = new RetryingTransactionHelper();
rthelper.setTransactionService(this.txService);
if (txtry != null) {
if (txtry.maxRetries() > 0)
rthelper.setMaxRetries(txtry.maxRetries());
if (txtry.minRetryWaitInMillis() > 0)
rthelper.setMinRetryWaitMs(txtry.minRetryWaitInMillis());
if (txtry.maxRetryWaitInMillis() > 0)
rthelper.setMaxRetryWaitMs(txtry.maxRetryWaitInMillis());
if (txtry.incRetryWaitInMillis() > 0)
rthelper.setRetryWaitIncrementMs(txtry.incRetryWaitInMillis());
}
if (txl != null && txl.getTimeoutInSeconds() > 0)
rthelper.setMaxExecutionMs(txl.getTimeoutInSeconds() * 1000L);
try {
this.logger.trace("source tx: {}", AlfrescoTransactionSupport.getTransactionId());
boolean readonly = txl != null && txl.isReadOnly() || txl == null && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
return rthelper.doInTransaction(rtcallback, readonly, txl != null);
} catch (RuntimeException re) {
// attempt to unwrap the exception
if (re.getMessage() == null) {
throw re;
} else if (re.getMessage().startsWith("Exception from transactional callback")) {
throw re.getCause();
} else if (re.getMessage().startsWith("Exception in Transaction")) {
throw re.getCause();
} else {
throw re;
}
} finally {
this.logger.debug("returned to tx: {}", AlfrescoTransactionSupport.getTransactionId());
}
}
}
@@ -0,0 +1,20 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractWarnOnceService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private Set<String> warned = new HashSet<>();
protected void warn(String key, String message, Object... arguments) {
if (this.warned.add(key))
this.logger.warn(message, arguments);
}
}
@@ -0,0 +1,12 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
public interface ApplicableParameterCallback<T extends Annotation> {
boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, T annotation, int parameterIndex);
}
@@ -0,0 +1,68 @@
package com.inteligr8.alfresco.annotations.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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, *")
public class AsyncAspect extends AbstractMethodAspect<Asynchronous> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
@Qualifier("async.mq")
private AsyncService durableAsyncService;
@Autowired
@Qualifier("async.thread")
private AsyncService volatileAsyncService;
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.Asynchronous) && execution(* *(..))")
public void isAsyncAnnotated() {
}
@Around("isAsyncAnnotated()")
public Object async(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("async({})", joinPoint);
Asynchronous async = this.getAnnotation(joinPoint, Asynchronous.class, true, true);
AsyncService asyncService = async.durable() ? this.durableAsyncService : this.volatileAsyncService;
if (!asyncService.isEnabled()) {
this.warn(joinPoint.toLongString(),
"Intercepted an @Asynchronous method call while the appropriate asynchronous service is not enabled; continuing synchronously");
return joinPoint.proceed();
} else if (asyncService.isCurrentThreadAsynchronous()) {
this.logger.debug("Intercepted an @Asynchronous method call while already asynchronous; continuing synchronously");
return joinPoint.proceed();
} else {
this.logger.trace("Intercepted an @Asynchronous method call; redirecting to the appropriate asynchronous service");
asyncService.push(joinPoint);
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
if (!this.isReturnTypeVoid(methodSig)) {
this.warn(joinPoint.toLongString(),
"An @Asynchronous method returns a value, which is not expected/allowed: {}",
methodSig.getMethod());
}
return null;
}
}
}
@@ -0,0 +1,114 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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")
public class AuthorizedAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.Authorized) && execution(* *(..))")
public void isAuthorizedAnnotated() {
}
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.AuthorizedAsSystem) && execution(* *(..))")
public void isAuthorizedAsSystemAnnotated() {
}
@Around("isAuthorizedAnnotated() || isAuthorizedAsSystemAnnotated()")
public Object runAs(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("runAs({})", joinPoint);
String runAsUser = this.getRunAsUser(joinPoint);
String currentRunAsUser = AuthenticationUtil.getRunAsUser();
if (currentRunAsUser != null && currentRunAsUser.equals(runAsUser)) {
this.logger.trace("The current context is already running as the specified user: {}", currentRunAsUser);
return joinPoint.proceed();
} else {
this.logger.debug("Changing runAs context: {} => {}", currentRunAsUser, runAsUser);
return this.runAs(joinPoint, runAsUser);
}
}
private String getRunAsUser(ProceedingJoinPoint joinPoint) {
if (!(joinPoint.getSignature() instanceof MethodSignature))
throw new IllegalStateException("The @Authorized annotation must be on methods and methods have signatures");
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
if (method.getAnnotation(AuthorizedAsSystem.class) != null) {
String runAs = AuthenticationUtil.getSystemUserName();
this.logger.trace("The @AuthorizedAsSystem method '{}' will run as: {}", method, runAs);
return runAs;
}
if (joinPoint.getThis() instanceof Authorizable) {
String runAs = StringUtils.trimToNull(((Authorizable) joinPoint.getThis()).authorizeAsUser());
if (runAs == null)
throw new IllegalArgumentException("A 'null' value is allowed from authorizeAsUser()");
this.logger.trace("The @Authorized method '{}' is Authorizable: {}", method, runAs);
return runAs;
}
Authorized runAsAnnotation = method.getAnnotation(Authorized.class);
String runAs = StringUtils.trimToNull(runAsAnnotation.value());
if (runAs != null && runAs.length() > 0) {
this.logger.trace("The @Authorized method '{}' must run as: {}", method, runAs);
return runAs;
}
this.logger.trace("The @Authorized method '{}' must run as system", method);
return AuthenticationUtil.getSystemUserName();
}
private Object runAs(final ProceedingJoinPoint joinPoint, String runAsUser) throws Throwable {
RunAsWork<Object> work = new RunAsWork<Object>() {
public Object doWork() throws Exception {
try {
return joinPoint.proceed();
} catch (Exception | Error e) {
throw e;
} catch (Throwable t) {
throw new RuntimeException("This should never happen", t);
}
}
};
try {
return AuthenticationUtil.runAs(work, runAsUser);
} catch (RuntimeException re) {
// attempt to unwrap the exception
if (re.getMessage() != null && re.getMessage().equals("Error during run as.")) {
throw re.getCause();
} else {
throw re;
}
}
}
}
@@ -0,0 +1,58 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary;
/**
* This aspect implements the IfChildAssociationIsPrimary annotation.
*
* @see com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary
*/
@Aspect
public class ChildIsPrimaryAspect extends AbstractMethodOrParameterAspect<IfChildAssociationIsPrimary> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Class<IfChildAssociationIsPrimary> getAnnotationClass() {
return IfChildAssociationIsPrimary.class;
}
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary) && execution(* *(..))")
public void isIfChildAssociationIsPrimaryMethodAnnotated() {
}
@Pointcut("execution(* *(@com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary (*), ..))")
public void isIfChildAssociationIsPrimaryParamAnnotated() {
}
@Around("isIfChildAssociationIsPrimaryMethodAnnotated() || isIfChildAssociationIsPrimaryParamAnnotated()")
public Object isChildAssocPrimary(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("isChildAssocPrimary({})", joinPoint);
return this.checkParameters(joinPoint, new ApplicableParameterCallback<IfChildAssociationIsPrimary>() {
@Override
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfChildAssociationIsPrimary annotation, int parameterIndex) {
Object arg = joinPoint.getArgs()[parameterIndex];
if (arg instanceof ChildAssociationRef) {
if (!((ChildAssociationRef)arg).isPrimary()) {
logger.debug("The child association '{}' is not primary; skipping method: {}", arg, method);
return false;
}
}
return true;
}
});
}
}
@@ -0,0 +1,70 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import org.alfresco.repo.lock.JobLockService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.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());
@Autowired
private JobLockService jobLockService;
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.JobSynchronized) && execution(* *(..))")
public void isJobSyncAnnotated() {
}
@Around("isJobSyncAnnotated()")
public Object jobSync(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("jobSync({})", joinPoint);
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 job lock: {}", lockQName);
String lockToken = this.jobLockService.getLock(lockQName, clusterSync.lockTimeoutInMillis(),
clusterSync.acquireWaitBetweenRetriesInMillis(), clusterSync.acquireMaxRetries());
try {
this.logger.trace("Acquired job lock: {}", lockQName);
return joinPoint.proceed();
} finally {
this.logger.debug("Releasing job lock: {}", lockQName);
this.jobLockService.releaseLock(lockToken, lockQName);
}
}
private QName getLockQName(JobSynchronized clusterSync, Method method) {
String lockName = StringUtils.trimToNull(clusterSync.value());
if (lockName != null) {
return QName.createQNameWithValidLocalName(NS, lockName);
} else {
String methodId = method.getDeclaringClass().getSimpleName() + "_" + method.getName();
return QName.createQNameWithValidLocalName(NS, methodId);
}
}
}
@@ -0,0 +1,132 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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")
public class NodeAspectAspect extends QNameBasedAspect<IfNodeHasAspect> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private DictionaryService dictionaryService;
@Autowired
private NodeService nodeService;
@Value("${inteligr8.cache.nodeAspectConstrainable.maxBeans}")
private int maxBeans;
@Override
public int getMaxBeansCache() {
return this.maxBeans;
}
@Override
public Class<IfNodeHasAspect> getAnnotationClass() {
return IfNodeHasAspect.class;
}
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfNodeHasAspect) && execution(* *(..))")
public void isIfAspectAnnotated() {
}
@Pointcut("execution(* *(@com.inteligr8.alfresco.annotations.IfNodeHasAspect (*), ..))")
public void isIfAspectParamAnnotated() {
}
@Around("isIfAspectAnnotated() || isIfAspectParamAnnotated()")
public Object isNodeAspect(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("isNodeAspect({})", joinPoint);
return this.checkParameters(joinPoint, new ApplicableParameterCallback<IfNodeHasAspect>() {
@Override
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfNodeHasAspect annotation, int parameterIndex) {
Object arg = joinPoint.getArgs()[parameterIndex];
Collection<NodeRef> nodeRefs = extractNodeRefs(arg);
if (nodeRefs == null)
return true;
QNameBasedCallback<IfNodeHasAspect> callback = new QNameBasedCallback<IfNodeHasAspect>() {
@Override
public boolean isConstrained() {
return joinPoint.getThis() instanceof NodeAspectConstrainable;
}
@Override
public String getConstrainableClassSimpleName() {
return NodeAspectConstrainable.class.getSimpleName();
}
@Override
public String getBeanName() {
return ((NodeAspectConstrainable) joinPoint.getThis()).getBeanName();
}
@Override
public Collection<? extends QNamePattern> constrainedQNames() {
return ((NodeAspectConstrainable) joinPoint.getThis()).constrainedAspects();
}
@Override
public Collection<QName> allPossibleQNames() {
return dictionaryService.getAllAspects();
}
@Override
public void addAllAncestors(Set<QName> qnames, QName qname) {
ClassDefinition aspectDef = dictionaryService.getAspect(qname);
while (aspectDef != null) {
qnames.add(aspectDef.getName());
aspectDef = aspectDef.getParentClassDefinition();
}
}
@Override
public String getAnnotationValue(IfNodeHasAspect annotation) {
return annotation.aspect();
}
};
Set<QName> aspects = getQNameCache(joinPoint, annotation, callback);
for (NodeRef nodeRef : nodeRefs) {
Set<QName> nodeAspects = nodeService.getAspects(nodeRef);
if (Collections.disjoint(aspects, nodeAspects)) {
logger.debug("The node '{}' aspects {} are not applicable; skipping method: {}", nodeRef, aspects, method);
return false;
}
}
return true;
}
});
}
}
@@ -0,0 +1,131 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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")
public class NodeTypeAspect extends QNameBasedAspect<IfNodeOfType> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private DictionaryService dictionaryService;
@Autowired
private NodeService nodeService;
@Value("${inteligr8.cache.nodeTypeConstrainable.maxBeans}")
private int maxBeans;
@Override
public int getMaxBeansCache() {
return this.maxBeans;
}
@Override
public Class<IfNodeOfType> getAnnotationClass() {
return IfNodeOfType.class;
}
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfNodeOfType) && execution(* *(..))")
public void isIfNodeTypeAnnotated() {
}
@Pointcut("execution(* *(@com.inteligr8.alfresco.annotations.IfNodeOfType (*), ..))")
public void isIfNodeTypeParamAnnotated() {
}
@Around("isIfNodeTypeAnnotated() || isIfNodeTypeParamAnnotated()")
public Object isNodeType(ProceedingJoinPoint joinPoint) throws Throwable {
this.logger.trace("isNodeType({})", joinPoint);
return this.checkParameters(joinPoint, new ApplicableParameterCallback<IfNodeOfType>() {
@Override
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfNodeOfType annotation, int parameterIndex) {
Object arg = joinPoint.getArgs()[parameterIndex];
Collection<NodeRef> nodeRefs = extractNodeRefs(arg);
if (nodeRefs == null)
return true;
QNameBasedCallback<IfNodeOfType> callback = new QNameBasedCallback<IfNodeOfType>() {
@Override
public boolean isConstrained() {
return joinPoint.getTarget() instanceof NodeTypeConstrainable;
}
@Override
public String getConstrainableClassSimpleName() {
return NodeTypeConstrainable.class.getSimpleName();
}
@Override
public String getBeanName() {
return ((NodeTypeConstrainable) joinPoint.getTarget()).getBeanName();
}
@Override
public Collection<? extends QNamePattern> constrainedQNames() {
return ((NodeTypeConstrainable) joinPoint.getTarget()).constrainedNodeTypes();
}
@Override
public Collection<QName> allPossibleQNames() {
return dictionaryService.getAllTypes();
}
@Override
public void addAllAncestors(Set<QName> qnames, QName qname) {
ClassDefinition typeDef = dictionaryService.getType(qname);
while (typeDef != null) {
qnames.add(typeDef.getName());
typeDef = typeDef.getParentClassDefinition();
}
}
@Override
public String getAnnotationValue(IfNodeOfType annotation) {
return annotation.type();
}
};
Set<QName> nodeTypes = getQNameCache(joinPoint, annotation, callback);
for (NodeRef nodeRef : nodeRefs) {
QName nodeType = nodeService.getType(nodeRef);
if (!nodeTypes.contains(nodeType)) {
logger.debug("The node '{}' type '{}' is not applicable; skipping method: {}", nodeRef, nodeType, method);
return false;
}
}
return true;
}
});
}
}
@@ -0,0 +1,44 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.alfresco.annotations.IfNotNull;
/**
* This aspect implements the IfNotNull annotation.
*
* @see com.inteligr8.alfresco.annotations.IfNotNull
*/
@Aspect
public class NotNullAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Pointcut("execution(* *(@com.inteligr8.alfresco.annotations.IfNotNull (*), ..))")
public void isNotNullAnnotated() {
}
@Around("isNotNullAnnotated()")
public Object isNotNull(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
for (int p = 0; p < method.getParameterCount(); p++) {
if (joinPoint.getArgs()[p] == null && method.getParameters()[p].isAnnotationPresent(IfNotNull.class)) {
this.logger.debug("A @IfNotNull parameter is `null`; skipping method: {}", method);
return null;
}
}
return joinPoint.proceed();
}
}
@@ -0,0 +1,119 @@
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.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.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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")
public class OperableNodeAspect extends AbstractMethodOrParameterAspect<IfNodeExists> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private NodeService nodeService;
@Override
public Class<IfNodeExists> getAnnotationClass() {
return IfNodeExists.class;
}
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfNodeExists) && execution(* *(..))")
public void isIfNodeExistsAnnotated() {
}
@Pointcut("execution(* *(@com.inteligr8.alfresco.annotations.IfNodeExists (*), ..))")
public void isIfNodeExistsParamAnnotated() {
}
@Around("isIfNodeExistsAnnotated() || isIfNodeExistsParamAnnotated()")
public Object isNodeOperable(ProceedingJoinPoint joinPoint) throws Throwable {
return this.checkParameters(joinPoint, new ApplicableParameterCallback<IfNodeExists>() {
@Override
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfNodeExists annotation, int parameterIndex) {
Object arg = joinPoint.getArgs()[parameterIndex];
Collection<NodeRef> nodeRefs = extractNodeRefs(arg);
if (nodeRefs == null)
return true;
logger.trace("Checking if nodes are operable: {}", nodeRefs);
for (NodeRef nodeRef : nodeRefs) {
if (!isOperableNode(nodeRef)) {
logger.debug("The node '{}' does not exist; skipping method: {}", nodeRef, method);
return false;
}
}
return true;
}
});
}
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;
}
}
private boolean isOperableNode(NodeRef nodeRef) {
if (!this.nodeService.exists(nodeRef)) {
this.logger.debug("The node '{}' does not exist", nodeRef);
return false;
} else if (this.nodeService.getNodeStatus(nodeRef).isDeleted()) {
this.logger.debug("The node '{}' was already deleted", nodeRef);
return false;
}
return true;
}
}
@@ -0,0 +1,136 @@
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 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;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.annotation.PostConstruct;
public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private NamespaceService namespaceService;
private SimpleCache<String, Set<QName>> qnameCache;
public abstract int getMaxBeansCache();
@PostConstruct
public void init() {
this.qnameCache = new DefaultSimpleCache<>(this.getMaxBeansCache(), this.getClass().getName());
}
public Set<QName> getQNameCache(ProceedingJoinPoint joinPoint, T annotation, QNameBasedCallback<T> callback) {
if (callback.isConstrained()) {
Set<QName> qnames = this.qnameCache.get(callback.getBeanName());
if (qnames != null) {
this.logger.trace("Using cache of qnames for bean: {}", callback.getBeanName());
return qnames;
}
// caching all types; expensive now; fast at runtime
qnames = new HashSet<>();
for (QNamePattern qnamePattern : callback.constrainedQNames()) {
if (qnamePattern instanceof QName) {
callback.addAllAncestors(qnames, (QName) qnamePattern);
} else {
for (QName qname : callback.allPossibleQNames()) {
if (qnamePattern.isMatch(qname)) {
callback.addAllAncestors(qnames, qname);
}
}
}
}
this.logger.debug("Caching @{} qnames for bean: {}: {}", annotation.getClass().getSimpleName(), callback.getBeanName(), qnames);
this.qnameCache.put(callback.getBeanName(), qnames);
return qnames;
} else if (callback.getAnnotationValue(annotation).length() > 0) {
Set<QName> qnames = this.qnameCache.get(joinPoint.getThis().getClass().getName());
if (qnames != null) {
this.logger.trace("Using cache of qnames for bean: {}", joinPoint.getThis().getClass());
return qnames;
}
qnames = new HashSet<>();
callback.addAllAncestors(qnames, QName.createQName(callback.getAnnotationValue(annotation), this.namespaceService));
this.logger.debug("Caching @{} node types for singleton: {}: {}", annotation.getClass().getSimpleName(), joinPoint.getThis().getClass(), qnames);
this.qnameCache.put(joinPoint.getThis().getClass().getName(), qnames);
return qnames;
} else {
throw new IllegalStateException("An @" + annotation.getClass().getSimpleName() + " must have a value or the class must implement " + callback.getConstrainableClassSimpleName());
}
}
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> {
boolean isConstrained();
String getBeanName();
String getConstrainableClassSimpleName();
Collection<? extends QNamePattern> constrainedQNames();
void addAllAncestors(Set<QName> qnames, QName qname);
Collection<QName> allPossibleQNames();
String getAnnotationValue(T annotation);
}
}
@@ -0,0 +1,186 @@
package com.inteligr8.alfresco.annotations.aspect;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
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;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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, *")
public class ThreadedAspect extends AbstractMethodAspect<Threaded> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private ThreadLocal<Set<String>> nested = ThreadLocal.withInitial(new Supplier<Set<String>>() {
public Set<String> get() {
return new HashSet<>();
}
});
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.Threaded) && execution(* *(..))")
public void isThreadedAnnotated() {
}
@Around("isThreadedAnnotated()")
public Object threaded(ProceedingJoinPoint joinPoint) throws Throwable {
// AspectJ does not recursively match annotations if the same thread is calling joinPoint.proceed()
// but when a different thread calls it, it doesn't know it is already processing the annotation
// so we are going to use a ThreadLocal to prevent recursion across threads
if (this.nested.get().contains(joinPoint.getSignature().toLongString()))
return joinPoint.proceed();
this.logger.trace("threaded({})", joinPoint);
Threaded threaded = this.getAnnotation(joinPoint, Threaded.class, true, true);
MergedThreadConfiguration threadConfig = new MergedThreadConfiguration(joinPoint, threaded);
ThreadFactoryBuilder tfbuilder = new ThreadFactoryBuilder()
.setPriority(threadConfig.getThreadPriority())
.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
logger.error("The thread '" + t.getName() + "' had an exception", e);
}
});
if (threaded.name().length() > 0)
tfbuilder.setNameFormat(threaded.name() + "-%d");
Integer concurrency = threadConfig.getConcurrency();
if (concurrency == null)
concurrency = threadConfig.getThreads();
BlockingQueue<Runnable> threadQueue = new ArrayBlockingQueue<>(threadConfig.getThreads().intValue());
ThreadPoolExecutor threadExecutor = new ThreadPoolExecutor(concurrency.intValue(), concurrency.intValue(),
1L, TimeUnit.SECONDS,
threadQueue,
tfbuilder.build());
Callable<Object> callable = this.createCallable(joinPoint);
this.logger.debug("Starting {} threads", threadConfig.getThreads());
for (int t = 0; t < threadConfig.getThreads().intValue(); t++)
threadExecutor.submit(callable);
threadExecutor.shutdown();
if (threaded.join()) {
long waitMillis = threaded.joinWaitMillis() == 0L ? 300000L : threaded.joinWaitMillis();
while (true) {
this.logger.debug("Blocking this thread until subthreads finish: {}", Thread.currentThread().getId());
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;
}
}
this.logger.debug("Subthreads running: {}; unblocking this thread: {}", threadExecutor.getActiveCount(), Thread.currentThread().getId());
}
return null;
}
private Callable<Object> createCallable(final ProceedingJoinPoint joinPoint) throws Throwable {
return new Callable<Object>() {
@Override
public Object call() throws Exception {
logger.debug("entering thread: {}", Thread.currentThread().getId());
// AspectJ does not recursively match annotations if the same thread is calling joinPoint.proceed()
// but when a different thread calls it, it doesn't know it is already processing the annotation
// so we are going to use a ThreadLocal to prevent recursion across threads
nested.get().add(joinPoint.getSignature().toLongString());
// we cannot use joinPoint.proceed() as it will only execute in 1 thread
// we need to recreate the method call, fresh, using reflection
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
try {
method.setAccessible(true);
return method.invoke(joinPoint.getThis(), joinPoint.getArgs());
} catch (Exception | Error e) {
logger.error("An unexpected issue occurred in thread #" + Thread.currentThread().getId(), e);
throw e;
} catch (Throwable t) {
logger.error("An unexpected issue occurred in thread #" + Thread.currentThread().getId(), t);
throw new RuntimeException("This should never happen", t);
} finally {
logger.trace("leaving thread: {}", Thread.currentThread().getId());
}
}
};
}
private class MergedThreadConfiguration implements Threadable {
private final Threaded threaded;
private final Threadable threadable;
public MergedThreadConfiguration(ProceedingJoinPoint joinPoint, Threaded threaded) {
this.threaded = threaded;
this.threadable = (joinPoint.getThis() instanceof Threadable) ? (Threadable) joinPoint.getThis() : null;
}
@Override
public Integer getThreads() {
if (this.threadable != null && this.threadable.getThreads() != null) {
return this.threadable.getThreads();
} else {
return this.threaded.threads();
}
}
@Override
public Integer getConcurrency() {
if (this.threadable != null && this.threadable.getConcurrency() != null) {
return this.threadable.getConcurrency();
} else if (this.threaded.concurrency() <= 0) {
return null;
} else {
return this.threaded.concurrency();
}
}
@Override
public Integer getThreadPriority() {
if (this.threadable != null && this.threadable.getThreadPriority() != null) {
return this.threadable.getThreadPriority();
} else {
return this.threaded.priority();
}
}
}
}
@@ -0,0 +1,25 @@
package com.inteligr8.alfresco.annotations.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
import com.inteligr8.alfresco.annotations.service.AsyncService;
public class AsyncJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
AsyncService asyncService = (AsyncService) context.getMergedJobDataMap().get("asyncService");
if (asyncService == null)
throw new JobExecutionException("An 'asyncService' object is required in the job map");
try {
asyncService.poll();
} catch (AsyncProcessException ape) {
throw new JobExecutionException(ape, true);
}
}
}
@@ -0,0 +1,11 @@
package com.inteligr8.alfresco.annotations.service;
public class AsyncProcessException extends Exception {
private static final long serialVersionUID = 8254359296736253436L;
public AsyncProcessException(String message, Throwable t) {
super(message, t);
}
}
@@ -0,0 +1,18 @@
package com.inteligr8.alfresco.annotations.service;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* @author brian@inteligr8.com
*/
public interface AsyncService {
boolean isEnabled();
boolean isCurrentThreadAsynchronous();
void poll() throws AsyncProcessException;
void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException;
}
@@ -0,0 +1,385 @@
package com.inteligr8.alfresco.annotations.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.version.common.VersionImpl;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.dictionary.CustomModelService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.version.Version;
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.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.inteligr8.alfresco.annotations.Threadable;
import com.inteligr8.alfresco.annotations.job.AsyncJob;
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
import com.inteligr8.alfresco.annotations.service.AsyncService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
/**
* @author brian@inteligr8.com
*/
public abstract class AbstractMqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations");
protected final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)");
protected final ObjectMapper om = new ObjectMapper();
@Value("${inteligr8.async.mq.enabled}")
protected boolean enabled;
@Value("${inteligr8.async.mq.workerThreads}")
protected int workerThreads;
@Value("${inteligr8.async.mq.url}")
protected String url;
@Value("${inteligr8.async.mq.username}")
protected String username;
@Value("${inteligr8.async.mq.password}")
protected String password;
@Value("${inteligr8.async.mq.queue}")
protected String queueName;
@Value("${inteligr8.async.mq.errorQueue}")
protected String errorQueueName;
@Value("${inteligr8.async.mq.clientId}")
protected String clientId;
@Value("${inteligr8.async.mq.pool.max}")
protected short maxConnections;
@Autowired
protected ActionService actionService;
@Autowired
protected ContentService contentService;
@Autowired
protected CustomModelService modelService;
@Autowired
protected DictionaryService dictionaryService;
@Autowired
protected NamespaceService namespaceService;
@Autowired
protected TransactionService txService;
protected String hostname;
protected SimpleCache<Pair<Class<?>, String>, Method> methodCache;
protected ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() {
@Override
public Boolean get() {
return false;
}
});
@Override
public final void afterPropertiesSet() throws Exception {
this.init();
}
@Override
public final void destroy() throws Exception {
this.uninit();
}
/**
* @PostConstruct does not work in ACS
*/
@PostConstruct
protected void init() {
if (!this.enabled)
return;
try {
this.hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException uhe) {
this.hostname = "unknown";
}
if (this.workerThreads <= 0)
throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive");
}
@PreDestroy
protected void uninit() {
}
@Override
protected void onBootstrap(ApplicationEvent event) {
if (!this.enabled)
return;
JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setKey(this.jobKey);
jobDetail.setRequestsRecovery(true);
jobDetail.setJobClass(AsyncJob.class);
jobDetail.getJobDataMap().put("asyncService", this);
Trigger trigger = TriggerBuilder.newTrigger()
.startNow()
.build();
try {
StdSchedulerFactory.getDefaultScheduler()
.scheduleJob(jobDetail, trigger);
} catch (SchedulerException se) {
this.logger.error("The MQ async service job failed to start; no asynchronous executions will be processed!", se);
}
}
@Override
protected void onShutdown(ApplicationEvent event) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.deleteJob(this.jobKey);
} catch (SchedulerException se) {
this.logger.warn("The MQ async service job failed to stop", se);
}
}
@Override
public boolean isEnabled() {
return this.enabled;
}
@Override
public Integer getThreads() {
return this.workerThreads;
}
@Override
public boolean isCurrentThreadAsynchronous() {
return this.isAsync.get();
}
protected Method findMethod(Class<?> clazz, String methodName) {
Pair<Class<?>, String> key = new Pair<>(clazz, methodName);
Method method = this.methodCache.get(key);
if (method != null) {
this.logger.trace("Found method in cache: {}", method);
return method;
}
this.logger.trace("Looping through bean type methods to find: {}", methodName);
for (Method amethod : clazz.getDeclaredMethods()) {
if (amethod.getName().equals(methodName)) {
this.logger.debug("Found and caching method: {} => {}", key, amethod);
this.methodCache.put(key, amethod);
return amethod;
}
}
throw new IllegalStateException("The bean (" + clazz + ") does not implement the method: " + methodName);
}
public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException {
this.logger.trace("push({})", joinPoint);
if (!(joinPoint.getSignature() instanceof MethodSignature))
throw new IllegalStateException("The join point must be on methods and methods have signatures");
Object bean = joinPoint.getThis();
this.logger.debug("Queuing for bean: {}", bean.getClass());
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
this.logger.debug("Queuing for method: {}", method);
this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs()));
}
public abstract void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException;
@SuppressWarnings({ "unchecked" })
protected Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> paramType = param.getType();
this.logger.trace("Unmarshaling parameter of type: {}", paramType);
if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
this.logger.trace("Unmarshaling primitive: {}", arg);
return arg;
} else if (Temporal.class.isAssignableFrom(paramType)) {
if (OffsetDateTime.class.isAssignableFrom(paramType)) {
return OffsetDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(arg.toString()));
} else if (ZonedDateTime.class.isAssignableFrom(paramType)) {
return ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(arg.toString()));
} else if (LocalDate.class.isAssignableFrom(paramType)) {
return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(arg.toString()));
} else if (LocalDateTime.class.isAssignableFrom(paramType)) {
return LocalDateTime.from(DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(arg.toString()));
} else if (Instant.class.isAssignableFrom(paramType)) {
return Instant.from(DateTimeFormatter.ISO_INSTANT.parse(arg.toString()));
} else if (LocalTime.class.isAssignableFrom(paramType)) {
return LocalTime.from(DateTimeFormatter.ISO_LOCAL_TIME.parse(arg.toString()));
} else if (OffsetTime.class.isAssignableFrom(paramType)) {
return OffsetTime.from(DateTimeFormatter.ISO_OFFSET_TIME.parse(arg.toString()));
} else {
throw new UnsupportedOperationException();
}
} else if (Version.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as JSON object: {}", arg);
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
Map<String, Serializable> versionPropertiesMap = (Map<String, Serializable>) argMap.get("properties");
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
Version version = new VersionImpl(versionPropertiesMap, nodeRef);
this.logger.trace("Unmarshaled version: {} = {}", param.getName(), version);
return version;
} else if (Action.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as JSON object: {}", arg);
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
String actionId = (String) argMap.get("actionId");
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
this.logger.trace("Unmarshaling action: {}, {}", actionId, nodeRef);
Action action = this.actionService.getAction(nodeRef, actionId);
this.logger.trace("Unmarshaled action: {} = {}", param.getName(), action);
return action;
} else if (Collection.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as JSON array: {}", arg);
return this.om.convertValue(arg, Collection.class);
} else if (Map.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as JSON object: {}", arg);
return this.om.convertValue(arg, Map.class);
} else if (QName.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as QName: {}", arg);
return QName.createQName((String) arg);
} else if (Enum.class.isAssignableFrom(paramType)) {
this.logger.trace("Unmarshaling as Enum: {}", arg);
Method cons = paramType.getDeclaredMethod("valueOf", String.class);
return cons.invoke(null, arg.toString());
} else {
this.logger.trace("Unmarshaling as POJO: {}", arg);
try {
Constructor<?> cons = paramType.getConstructor(String.class);
return cons.newInstance(arg.toString());
} catch (NoSuchMethodException nsme) {
Method method = paramType.getDeclaredMethod("valueOf", String.class);
return method.invoke(null, arg.toString());
}
}
}
protected Object marshal(Object arg) {
if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
return arg;
} else if (arg instanceof Temporal) {
return arg.toString();
} else if (arg instanceof Version) {
Version version = (Version) arg;
Map<String, Object> map = new HashMap<>();
map.put("nodeRef", version.getFrozenStateNodeRef());
map.put("properties", version.getVersionProperties());
this.logger.trace("Marshaling Version as JSON object: {}", map);
return this.om.convertValue(map, String.class);
} else if (arg instanceof Action) {
Action action = (Action) arg;
Map<String, Object> map = new HashMap<>();
map.put("nodeRef", action.getNodeRef());
map.put("actionId", action.getId());
this.logger.trace("Marshaling Action as JSON object: {}", map);
return this.om.convertValue(map, String.class);
} else if (arg instanceof Collection<?>) {
List<Object> list = new ArrayList<>(((Collection<?>)arg).size());
for (Object obj : (Collection<?>) arg)
list.add(this.marshal(obj));
this.logger.trace("Marshaling Java Collection as JSON array: {}", list);
return this.om.convertValue(list, String.class);
} else if (arg instanceof Map<?, ?>) {
Map<Object, Object> map = new HashMap<>();
for (Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) {
Object key = this.marshal(entry.getKey());
Object value = this.marshal(entry.getValue());
map.put(key, value);
}
this.logger.trace("Marshaling Java Map as JSON object: {}", map);
return this.om.convertValue(map, String.class);
} else {
this.logger.trace("Marshaling Java object as JSON object: {}", arg);
return this.om.convertValue(arg, String.class);
}
}
protected M2Model loadModel(NodeRef nodeRef) throws IOException {
ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
InputStream istream = creader.getContentInputStream();
try {
return M2Model.createModel(istream);
} finally {
istream.close();
}
}
}
@@ -0,0 +1,189 @@
package com.inteligr8.alfresco.annotations.service.impl;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.JobDetailImpl;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import com.inteligr8.alfresco.annotations.job.AsyncJob;
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
import com.inteligr8.alfresco.annotations.service.AsyncService;
import jakarta.annotation.PostConstruct;
/**
* This class provides a non-persistent alternative to MQ for asynchronous method
* execution.
*
* @author brian@inteligr8.com
*/
@Component("async.thread")
public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations");
@Value("${inteligr8.async.workableThreads}")
protected int queueSize;
@Value("${inteligr8.async.workerThreads}")
protected byte poolSize;
private LinkedBlockingQueue<Runnable> queue;
private ThreadPoolExecutor pool;
private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() {
@Override
public Boolean get() {
return false;
}
});
@Override
public void afterPropertiesSet() throws Exception {
this.init();
}
/**
* @PostConstruct doesn't work in ACS for whatever reason
*/
@PostConstruct
protected void init() {
this.queue = new LinkedBlockingQueue<>(this.queueSize);
}
protected void onBootstrap(ApplicationEvent event) {
JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setKey(this.jobKey);
jobDetail.setRequestsRecovery(true);
jobDetail.setJobClass(AsyncJob.class);
jobDetail.getJobDataMap().put("asyncService", this);
Trigger trigger = TriggerBuilder.newTrigger()
.startNow()
.build();
try {
StdSchedulerFactory.getDefaultScheduler()
.scheduleJob(jobDetail, trigger);
} catch (SchedulerException se) {
this.logger.error("The async service job failed to start; no asynchronous executions will be processed!", se);
}
}
@Override
protected void onShutdown(ApplicationEvent event) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.deleteJob(this.jobKey);
} catch (SchedulerException se) {
this.logger.warn("The MQ async service job failed to stop", se);
}
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isCurrentThreadAsynchronous() {
return this.isAsync.get();
}
@Override
public void poll() throws AsyncProcessException {
this.logger.trace("poll()");
this.isAsync.set(true);
this.pool = new ThreadPoolExecutor(1, this.poolSize, 5L, TimeUnit.SECONDS, this.queue);
try {
while (!this.pool.isTerminated()) {
this.logger.debug("Perpetually waiting for thread pool to terminate ...");
this.pool.awaitTermination(300L, TimeUnit.SECONDS);
}
this.logger.info("The Async service thread pool terminated");
} catch (InterruptedException ie) {
this.logger.info("The Async service thread pool was interrupted");
}
}
@Transactional
protected void executeWork(Object callbackBean, Method callbackMethod, List<Object> args) {
try {
callbackMethod.invoke(callbackBean, args.toArray());
} catch (IllegalAccessException iae) {
this.logger.error("A bean method was not accessible (public)");
this.logger.warn("The bean '{}' method '{}' is not accessible: {}", callbackBean.getClass(), callbackMethod, iae.getMessage());
} catch (InvocationTargetException ite) {
this.logger.error("A bean method execution failed");
this.logger.warn("The bean '{}' method '{}' execution failed: {}", callbackBean.getClass(), callbackMethod, ite.getMessage());
}
}
public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException {
this.logger.trace("push({})", joinPoint);
if (!(joinPoint.getSignature() instanceof MethodSignature))
throw new IllegalStateException("The join point must be on methods and methods have signatures");
Object bean = joinPoint.getThis();
this.logger.debug("Queuing for bean: {}", bean.getClass());
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
Method method = methodSig.getMethod();
this.logger.debug("Queuing for method: {}", method);
this.push(bean, method, Arrays.asList(joinPoint.getArgs()));
}
public void push(Object callbackBean, Method callbackMethod, List<Object> args) throws AsyncProcessException {
this.logger.trace("push({}, {}, {})", callbackBean, callbackMethod, args);
RunAsWork<Void> work = new RunAsWork<Void>() {
@Override
public Void doWork() {
executeWork(callbackBean, callbackMethod, args);
return null;
}
};
final String runAs = AuthenticationUtil.getRunAsUser();
Runnable runnable = new Runnable() {
@Override
public void run() {
// run the thread with the same authentication context as the initiating user
AuthenticationUtil.runAs(work, runAs);
}
};
this.queue.offer(runnable);
}
}
@@ -0,0 +1,26 @@
package com.inteligr8.alfresco.annotations.util;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
public interface JtaTransactionalAnnotationAdapter extends TransactionalAnnotationAdapter {
default boolean isReadOnly() {
return false;
}
Propagation getPropagation();
default Isolation getIsolation() {
return Isolation.DEFAULT;
}
default int getTimeoutInSeconds() {
return 0;
}
Class<? extends Throwable>[] getRollbackFor();
Class<? extends Throwable>[] getNoRollbackFor();
}
@@ -0,0 +1,32 @@
package com.inteligr8.alfresco.annotations.util;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.util.Pair;
public class MapUtils {
public static <K, V> Map<K, V> build(Pair<K, V>... pairs) {
Map<K, V> map = new HashMap<>();
for (Pair<K, V> pair : pairs) {
map.put(pair.getFirst(), pair.getSecond());
}
return map;
}
public static Map<String, String> build(String... keyValuePairs) {
if (keyValuePairs.length % 2 == 1)
throw new IllegalArgumentException();
Map<String, String> map = new HashMap<>();
for (int pair = 0; pair < keyValuePairs.length / 2; pair++) {
int base = pair * 2;
map.put(keyValuePairs[base], keyValuePairs[base + 1]);
}
return map;
}
}
@@ -0,0 +1,50 @@
package com.inteligr8.alfresco.annotations.util;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class SpringTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter {
private final Transactional txl;
public SpringTransactionalAnnotationAdapter(Transactional txl) {
this.txl = txl;
}
@Override
public boolean isReadOnly() {
return this.txl.readOnly();
}
@Override
public Propagation getPropagation() {
return this.txl.propagation();
}
@Override
public Isolation getIsolation() {
return this.txl.isolation();
}
@Override
public int getTimeoutInSeconds() {
return this.txl.timeout();
}
@Override
public Class<? extends Throwable>[] getRollbackFor() {
return this.txl.rollbackFor();
}
@Override
public Class<? extends Throwable>[] getNoRollbackFor() {
return this.txl.noRollbackFor();
}
}
@@ -0,0 +1,20 @@
package com.inteligr8.alfresco.annotations.util;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
public interface TransactionalAnnotationAdapter {
boolean isReadOnly();
Propagation getPropagation();
Isolation getIsolation();
int getTimeoutInSeconds();
Class<? extends Throwable>[] getRollbackFor();
Class<? extends Throwable>[] getNoRollbackFor();
}
+18
View File
@@ -0,0 +1,18 @@
<aspectj>
<aspects>
<!-- These must be in precedence order; highest to lowest -->
<aspect name="com.inteligr8.alfresco.annotations.aspect.NotNullAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.ThreadedAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" />
<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" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.ChildIsPrimaryAspect" />
<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" />
</aspects>
</aspectj>
@@ -0,0 +1,24 @@
inteligr8.annotations.aspectj.scanPackages=com.inteligr8.alfresco.annotations
# should @Asynchronous be joined with MQ (or local thread pool)
inteligr8.async.mq.enabled=true
# threads to execute @Asynchronous methods (when MQ disabled above or by method)
inteligr8.async.workableThreads=100
inteligr8.async.workerThreads=5
# threads to execute @Asynchronous methods (when MQ enabled above; 0 disables execution)
inteligr8.async.mq.workerThreads=1
# MQ settings
inteligr8.async.mq.url=${messaging.broker.url}
inteligr8.async.mq.username=${messaging.broker.username}
inteligr8.async.mq.password=${messaging.broker.password}
inteligr8.async.mq.queue=inteligr8.acs.async
inteligr8.async.mq.errorQueue=inteligr8.acs.asyncError
inteligr8.async.mq.clientId=inteligr8-async
inteligr8.async.mq.pool.max=2
inteligr8.cache.nodeTypeConstrainable.maxBeans=16
inteligr8.cache.nodeAspectConstrainable.maxBeans=16
@@ -0,0 +1 @@
log4j.logger.com.inteligr8.alfresco.annotations=info
@@ -0,0 +1,2 @@
logger.inteligr8-annotations.name=com.inteligr8.alfresco.annotations
logger.inteligr8-annotations.level=info
@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Use this file for beans to be loaded in whatever order Alfresco/Spring decides -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- Enable Spring annotation scanning for classes in package -->
<context:component-scan base-package="com.inteligr8.alfresco.annotations" />
</beans>
@@ -0,0 +1,222 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
public class AbstractTransactionalTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "An unexpected transaction: " + AlfrescoTransactionSupport.getTransactionId());
this.tryOutsideTx();
this.tryWithinTx();
this.tryWithinReadonlyTx();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
private void tryOutsideTx() {
this.logger.info("Running outside TX test");
this.tryDefaultTransactional(null, false);
this.tryReadOnlyTransactional(null, false);
this.tryRetryOnlyTransactional(null);
this.trySupportsTransactional(null, false);
this.tryRequiresNewTransactional(null, false);
this.tryRequiredTransactional(null, false);
this.tryNeverTransactional(null);
try {
this.tryNoSupportsTransactional();
} catch (IllegalTransactionStateException uoe) {
throw new IllegalStateException();
}
try {
this.tryMandatoryTransactional(null, false);
throw new IllegalStateException();
} catch (IllegalTransactionStateException itse) {
// suppress
}
}
@Transactional
private void tryWithinTx() {
this.logger.info("Running inside read/write TX test");
String txId = AlfrescoTransactionSupport.getTransactionId();
boolean readonly = false;
this.tryDefaultTransactional(txId, readonly);
this.tryReadOnlyTransactional(txId, readonly);
this.tryRetryOnlyTransactional(txId);
this.trySupportsTransactional(txId, readonly);
this.tryRequiresNewTransactional(txId, readonly);
this.tryRequiredTransactional(txId, readonly);
this.tryMandatoryTransactional(txId, readonly);
try {
this.tryNoSupportsTransactional();
throw new IllegalStateException();
} catch (IllegalTransactionStateException uoe) {
// suppress
}
try {
this.tryNeverTransactional(txId);
throw new IllegalStateException();
} catch (IllegalTransactionStateException itse) {
// suppress
}
}
@Transactional(readOnly = true)
private void tryWithinReadonlyTx() {
this.logger.info("Running inside read-only TX test");
String txId = AlfrescoTransactionSupport.getTransactionId();
boolean readonly = true;
this.tryDefaultTransactional(txId, readonly);
this.tryReadOnlyTransactional(txId, readonly);
this.tryRetryOnlyTransactional(txId);
this.trySupportsTransactional(txId, readonly);
this.tryRequiresNewTransactional(txId, readonly);
this.tryRequiredTransactional(txId, readonly);
try {
this.tryNoSupportsTransactional();
throw new IllegalStateException();
} catch (IllegalTransactionStateException uoe) {
// suppress
}
try {
this.tryMandatoryTransactional(txId, readonly);
throw new IllegalStateException();
} catch (IllegalTransactionStateException itse) {
// suppress
}
try {
this.tryNeverTransactional(txId);
throw new IllegalStateException();
} catch (IllegalTransactionStateException itse) {
// suppress
}
}
@Transactional
private void tryDefaultTransactional(String originTxId, boolean originReadonly) {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction");
if (originTxId != null) {
if (originReadonly) {
// changed from readonly to read/write; need new TX
Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId);
} else {
// no changes; same TX
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
}
}
}
@Transactional(readOnly = true)
private void tryReadOnlyTransactional(String originTxId, boolean originReadonly) {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a readonly transaction");
if (originTxId != null) {
if (originReadonly) {
// no changes; same TX
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
} else {
// changed from read/write to readonly; need new TX
Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId);
}
}
}
@Transactional(propagation = Propagation.SUPPORTS)
private void trySupportsTransactional(String originTxId, boolean originReadonly) {
if (originTxId == null) {
Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction");
} else {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction");
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
}
}
@Transactional(propagation = Propagation.REQUIRED)
private void tryRequiredTransactional(String originTxId, boolean originReadonly) {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction");
if (originTxId != null) {
if (originReadonly) {
// changed from readonly to read/write; need new TX
Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId);
} else {
// no changes; same TX
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
}
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void tryRequiresNewTransactional(String originTxId, boolean originReadonly) {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction");
if (originTxId != null)
Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId);
}
@Transactional(propagation = Propagation.MANDATORY)
private void tryMandatoryTransactional(String originTxId, boolean originReadonly) {
if (originTxId == null) {
throw new IllegalStateException();
} else {
Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction");
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction");
}
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
private void tryNoSupportsTransactional() {
Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction");
Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction");
}
@Transactional(propagation = Propagation.NEVER)
private void tryNeverTransactional(String originTxId) {
if (originTxId == null) {
Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction");
} else {
throw new IllegalStateException();
}
}
@TransactionalRetryable
private void tryRetryOnlyTransactional(String originTxId) {
if (originTxId == null) {
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId() != null, "Expected a new transaction");
} else {
Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId);
}
}
}
@@ -0,0 +1,76 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.error.AlfrescoRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class AsynchronousTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private long mainThreadId;
private Object obj;
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
this.mainThreadId = Thread.currentThread().getId();
this.logger.info("Running in thread: {}", this.mainThreadId);
this.tryThreadPool();
this.tryMq();
try {
Thread.sleep(500L);
} catch (InterruptedException ie) {
throw new AlfrescoRuntimeException("This should never happen", ie);
}
this.obj = new Object();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Asynchronous(durable = false)
private void tryThreadPool() {
this.logger.info("Running in another thread: {}", Thread.currentThread().getId());
Assert.isTrue(Thread.currentThread().getId() != this.mainThreadId, "This method is not executing in a separate thread");
Assert.isNull(this.obj, "The random value is expected to be 'null'");
try {
Thread.sleep(1000L);
} catch (InterruptedException ie) {
throw new AlfrescoRuntimeException("This should never happen", ie);
}
Assert.isTrue(this.obj != null, "The random value is not expected to be 'null'");
}
@Asynchronous
private void tryMq() {
this.logger.info("Running in another thread: {}", Thread.currentThread().getId());
Assert.isTrue(Thread.currentThread().getId() != this.mainThreadId, "This method is not executing in a separate thread");
Assert.isNull(this.obj, "The random value is expected to be 'null'");
try {
Thread.sleep(1000L);
} catch (InterruptedException ie) {
throw new AlfrescoRuntimeException("This should never happen", ie);
}
Assert.isTrue(this.obj != null, "The random value is not expected to be 'null'");
}
}
@@ -0,0 +1,52 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class AuthorizableTest extends AbstractLifecycleBean implements Authorizable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AuthenticationUtil.getRunAsUser(), "An unexpected authorization: " + AuthenticationUtil.getRunAsUser());
this.tryAuthorized();
this.tryAuthorizedAsSystem();
this.tryDoubleAuthorized();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String authorizeAsUser() {
return "admin";
}
@Authorized
private void tryAuthorized() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue("admin".equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: admin != " + AuthenticationUtil.getRunAsUser());
}
@AuthorizedAsSystem
private void tryAuthorizedAsSystem() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
@Authorized
private void tryDoubleAuthorized() {
this.tryAuthorizedAsSystem();
Assert.isTrue("admin".equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: admin != " + AuthenticationUtil.getRunAsUser());
}
}
@@ -0,0 +1,54 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class AuthorizedTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AuthenticationUtil.getRunAsUser(), "An unexpected authorization: " + AuthenticationUtil.getRunAsUser());
this.tryAuthorized();
this.tryAdminAuthorized();
this.tryAuthorizedAsSystem();
this.tryDoubleAuthorized();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Authorized
private void tryAuthorized() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
@Authorized("admin")
private void tryAdminAuthorized() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue("admin".equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: admin != " + AuthenticationUtil.getRunAsUser());
}
@AuthorizedAsSystem
private void tryAuthorizedAsSystem() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
@Authorized("admin")
private void tryDoubleAuthorized() {
this.tryAuthorizedAsSystem();
Assert.isTrue("admin".equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: admin != " + AuthenticationUtil.getRunAsUser());
}
}
@@ -0,0 +1,114 @@
package com.inteligr8.alfresco.annotations;
import java.util.UUID;
import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfChildAssocIsPrimaryTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final MutableBoolean executed = new MutableBoolean();
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef mockNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, UUID.randomUUID().toString());
QName mockName = QName.createQNameWithValidLocalName(NamespaceService.ALFRESCO_URI, "test");
ChildAssociationRef mockPrimaryAssoc = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, mockNodeRef, mockName, mockNodeRef, true, 0);
ChildAssociationRef mockSecondaryAssoc = new ChildAssociationRef(ContentModel.ASSOC_CONTAINS, mockNodeRef, mockName, mockNodeRef, false, 0);
this.executed.setFalse();
this.tryMethodNoParam();
if (this.executed.isFalse())
throw new IllegalArgumentException();
this.executed.setFalse();
this.tryMethodNoMatchingParam("test");
if (this.executed.isFalse())
throw new IllegalArgumentException();
this.tryMethodWithOnePrimaryParam(mockPrimaryAssoc);
this.tryMethodWithOneNullParam(null);
this.tryMethodWithOneNonPrimaryParam(mockSecondaryAssoc);
this.tryMethodWithTwoPrimaryParams(mockPrimaryAssoc, "test", mockPrimaryAssoc);
this.tryMethodWithTwoNullPrimaryParams(null, "test", mockPrimaryAssoc);
this.tryMethodWithTwoNullPrimaryParams(mockPrimaryAssoc, "test", null);
this.tryMethodWithTwoMixedParams(mockPrimaryAssoc, "test", mockSecondaryAssoc);
this.tryMethodWithTwoMixedParams(mockSecondaryAssoc, "test", mockSecondaryAssoc);
this.tryParamWithPrimaryParam(mockPrimaryAssoc, mockPrimaryAssoc);
this.tryParamWithPrimaryParam(mockPrimaryAssoc, mockSecondaryAssoc);
this.tryParamWithPrimaryParam(mockSecondaryAssoc, mockPrimaryAssoc);
this.tryParamWithNullParam(null, mockPrimaryAssoc);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@IfChildAssociationIsPrimary
private void tryMethodNoParam() {
this.executed.setTrue();
}
@IfChildAssociationIsPrimary
private void tryMethodNoMatchingParam(String test) {
this.executed.setTrue();
}
@IfChildAssociationIsPrimary
private void tryMethodWithOnePrimaryParam(ChildAssociationRef childAssocRef) {
Assert.isTrue(childAssocRef.isPrimary(), "Unexpected non-primary child association");
}
@IfChildAssociationIsPrimary
private void tryMethodWithOneNullParam(ChildAssociationRef childAssocRef) {
Assert.isTrue(childAssocRef == null, "Unexpected child association");
}
@IfChildAssociationIsPrimary
private void tryMethodWithOneNonPrimaryParam(ChildAssociationRef childAssocRef) {
throw new UnsupportedOperationException();
}
@IfChildAssociationIsPrimary
private void tryMethodWithTwoPrimaryParams(ChildAssociationRef childAssocRef1, String test, ChildAssociationRef childAssocRef2) {
Assert.isTrue(childAssocRef1.isPrimary() && childAssocRef2.isPrimary(), "Unexpected non-primary child association");
}
@IfChildAssociationIsPrimary
private void tryMethodWithTwoNullPrimaryParams(ChildAssociationRef childAssocRef1, String test, ChildAssociationRef childAssocRef2) {
Assert.isTrue(childAssocRef1 == null || childAssocRef1.isPrimary(), "Unexpected non-primary child association");
Assert.isTrue(childAssocRef2 == null || childAssocRef2.isPrimary(), "Unexpected non-primary child association");
}
@IfChildAssociationIsPrimary
private void tryMethodWithTwoMixedParams(ChildAssociationRef childAssocRef1, String test, ChildAssociationRef childAssocRef2) {
throw new UnsupportedOperationException();
}
private void tryParamWithPrimaryParam(@IfChildAssociationIsPrimary ChildAssociationRef childAssocRef1, ChildAssociationRef childAssocRef2) {
Assert.isTrue(childAssocRef1.isPrimary(), "Unexpected non-primary child association");
}
private void tryParamWithNullParam(@IfChildAssociationIsPrimary ChildAssociationRef childAssocRef1, ChildAssociationRef childAssocRef2) {
Assert.isTrue(childAssocRef1 == null, "Unexpected child association");
}
}
@@ -0,0 +1,111 @@
package com.inteligr8.alfresco.annotations;
import java.util.Arrays;
import java.util.Collection;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;
import org.alfresco.repo.nodelocator.NodeLocatorService;
import org.alfresco.repo.nodelocator.SharedHomeNodeLocator;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QNamePattern;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfNodeAspectConstrainableTest extends AbstractLifecycleBean implements NodeAspectConstrainable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private NamespaceService namespaceService;
@Autowired
private NodeService nodeService;
@Autowired
private NodeLocatorService nodeLocatorService;
private final MutableBoolean executed = new MutableBoolean();
private String beanName;
@AuthorizedAsSystem
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef rootNodeRef = this.nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
NodeRef chNodeRef = this.nodeLocatorService.getNode(CompanyHomeNodeLocator.NAME, null, null);
NodeRef sharedNodeRef = this.nodeLocatorService.getNode(SharedHomeNodeLocator.NAME, null, null);
this.tryNodes(chNodeRef, sharedNodeRef);
this.tryNode(sharedNodeRef);
this.executed.setFalse();
this.tryNull(null);
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNotNode(rootNodeRef);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String getBeanName() {
return this.beanName;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public NamespaceService getNamespaceService() {
return this.namespaceService;
}
@Override
public Collection<? extends QNamePattern> constrainedAspects() {
return Arrays.asList(ContentModel.ASPECT_AUDITABLE);
}
@IfNodeHasAspect
private void tryNodes(NodeRef... nodeRefs) {
for (NodeRef nodeRef : nodeRefs) {
Assert.isTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE), "Missing 'cm:auditable' aspect: " + nodeRef);
}
}
private void tryNode(@IfNodeHasAspect NodeRef nodeRef) {
Assert.isTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE), "Missing 'cm:auditable' aspect: " + nodeRef);
}
@IfNodeHasAspect
private void tryNotNodes(NodeRef... nodeRefs) {
throw new UnsupportedOperationException();
}
@IfNodeHasAspect
private void tryNull(NodeRef nodeRef) {
this.executed.setTrue();
}
private void tryNotNode(@IfNodeHasAspect NodeRef nodeRef) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,87 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.nodelocator.NodeLocatorService;
import org.alfresco.repo.nodelocator.SharedHomeNodeLocator;
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.cmr.repository.StoreRef;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfNodeAspectTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private NodeService nodeService;
@Autowired
private NodeLocatorService nodeLocatorService;
private final MutableBoolean executed = new MutableBoolean();
@AuthorizedAsSystem
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef rootNodeRef = this.nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
NodeRef sharedNodeRef = this.nodeLocatorService.getNode(SharedHomeNodeLocator.NAME, null, null);
ChildAssociationRef sharedParentRef = this.nodeService.getPrimaryParent(sharedNodeRef);
this.tryAuditables(sharedNodeRef);
this.tryAuditable(sharedNodeRef);
this.tryAuditable(sharedParentRef);
this.executed.setFalse();
this.tryNull(null);
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNotAuditable(rootNodeRef);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@IfNodeHasAspect(aspect = "cm:auditable")
private void tryAuditables(NodeRef... nodeRefs) {
for (NodeRef nodeRef : nodeRefs) {
Assert.isTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE), "Missing 'cm:auditable' aspect: " + nodeRef);
}
}
private void tryAuditable(@IfNodeHasAspect(aspect = "cm:auditable") NodeRef nodeRef) {
Assert.isTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE), "Missing 'cm:auditable' aspect: " + nodeRef);
}
private void tryAuditable(@IfNodeHasAspect(aspect = "cm:auditable") ChildAssociationRef childAssocRef) {
NodeRef nodeRef = childAssocRef.getChildRef();
Assert.isTrue(this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_AUDITABLE), "Missing 'cm:auditable' aspect: " + nodeRef);
}
@IfNodeHasAspect(aspect = "cm:auditable")
private void tryNotAuditables(NodeRef... nodeRefs) {
throw new UnsupportedOperationException();
}
@IfNodeHasAspect(aspect = "cm:auditable")
private void tryNull(NodeRef nodeRef) {
this.executed.setTrue();
}
private void tryNotAuditable(@IfNodeOfType(type = "cm:auditable") NodeRef nodeRef) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,82 @@
package com.inteligr8.alfresco.annotations;
import java.util.UUID;
import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;
import org.alfresco.repo.nodelocator.NodeLocatorService;
import org.alfresco.repo.nodelocator.SharedHomeNodeLocator;
import org.alfresco.repo.nodelocator.SitesHomeNodeLocator;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfNodeExistsTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private NodeService nodeService;
@Autowired
private NodeLocatorService nodeLocatorService;
private final MutableBoolean executed = new MutableBoolean();
@AuthorizedAsSystem
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef mockNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, UUID.randomUUID().toString());
NodeRef rootNodeRef = this.nodeLocatorService.getNode(CompanyHomeNodeLocator.NAME, null, null);
NodeRef sharedNodeRef = this.nodeLocatorService.getNode(SharedHomeNodeLocator.NAME, null, null);
NodeRef sitesNodeRef = this.nodeLocatorService.getNode(SitesHomeNodeLocator.NAME, null, null);
this.tryNodesExist(rootNodeRef, sharedNodeRef, sitesNodeRef);
this.tryNodeExists(sharedNodeRef);
this.executed.setFalse();
this.tryNull(null);
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNodesNotExist(mockNodeRef);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@IfNodeExists
private void tryNodesExist(NodeRef... nodeRefs) {
for (NodeRef nodeRef : nodeRefs) {
Assert.isTrue(nodeRef != null, "Expected node reference: " + nodeRef);
Assert.isTrue(this.nodeService.exists(nodeRef) && !this.nodeService.getNodeStatus(nodeRef).isDeleted(), "Expected node: " + nodeRef);
}
}
private void tryNodeExists(@IfNodeExists NodeRef nodeRef) {
Assert.isTrue(nodeRef != null, "Expected node reference: " + nodeRef);
Assert.isTrue(this.nodeService.exists(nodeRef) && !this.nodeService.getNodeStatus(nodeRef).isDeleted(), "Expected node: " + nodeRef);
}
@IfNodeExists
private void tryNull(NodeRef nodeRef) {
this.executed.setTrue();
}
@IfNodeExists
private void tryNodesNotExist(NodeRef... nodeRefs) {
throw new IllegalStateException();
}
}
@@ -0,0 +1,118 @@
package com.inteligr8.alfresco.annotations;
import java.util.Arrays;
import java.util.Collection;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;
import org.alfresco.repo.nodelocator.NodeLocatorService;
import org.alfresco.repo.nodelocator.SharedHomeNodeLocator;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfNodeTypeConstrainableTest extends AbstractLifecycleBean implements NodeTypeConstrainable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private DictionaryService dictionaryService;
@Autowired
private NamespaceService namespaceService;
@Autowired
private NodeService nodeService;
@Autowired
private NodeLocatorService nodeLocatorService;
private final MutableBoolean executed = new MutableBoolean();
private String beanName;
@AuthorizedAsSystem
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef rootNodeRef = this.nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
NodeRef chNodeRef = this.nodeLocatorService.getNode(CompanyHomeNodeLocator.NAME, null, null);
NodeRef sharedNodeRef = this.nodeLocatorService.getNode(SharedHomeNodeLocator.NAME, null, null);
this.tryNodes(chNodeRef, sharedNodeRef);
this.tryNode(sharedNodeRef);
this.executed.setFalse();
this.tryNull(null);
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNotNode(rootNodeRef);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String getBeanName() {
return this.beanName;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public NamespaceService getNamespaceService() {
return this.namespaceService;
}
@Override
public Collection<? extends QNamePattern> constrainedNodeTypes() {
return Arrays.asList(ContentModel.TYPE_FOLDER);
}
@IfNodeOfType
private void tryNodes(NodeRef... nodeRefs) {
for (NodeRef nodeRef : nodeRefs) {
QName nodeType = this.nodeService.getType(nodeRef);
Assert.isTrue(this.dictionaryService.isSubClass(nodeType, ContentModel.TYPE_FOLDER), "Unexpected node type: " + nodeType);
}
}
private void tryNode(@IfNodeOfType NodeRef nodeRef) {
QName nodeType = this.nodeService.getType(nodeRef);
Assert.isTrue(this.dictionaryService.isSubClass(nodeType, ContentModel.TYPE_FOLDER), "Unexpected node type: " + nodeType);
}
@IfNodeOfType
private void tryNotNodes(NodeRef... nodeRefs) {
throw new UnsupportedOperationException();
}
@IfNodeOfType
private void tryNull(NodeRef nodeRef) {
this.executed.setTrue();
}
private void tryNotNode(@IfNodeOfType NodeRef nodeRef) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,96 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.nodelocator.CompanyHomeNodeLocator;
import org.alfresco.repo.nodelocator.NodeLocatorService;
import org.alfresco.repo.nodelocator.SharedHomeNodeLocator;
import org.alfresco.service.cmr.dictionary.DictionaryService;
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.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QName;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class IfNodeTypeTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private DictionaryService dictionaryService;
@Autowired
private NodeService nodeService;
@Autowired
private NodeLocatorService nodeLocatorService;
private final MutableBoolean executed = new MutableBoolean();
@AuthorizedAsSystem
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
NodeRef rootNodeRef = this.nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
NodeRef chNodeRef = this.nodeLocatorService.getNode(CompanyHomeNodeLocator.NAME, null, null);
NodeRef sharedNodeRef = this.nodeLocatorService.getNode(SharedHomeNodeLocator.NAME, null, null);
ChildAssociationRef sharedParentRef = this.nodeService.getPrimaryParent(sharedNodeRef);
this.tryFolders(chNodeRef, sharedNodeRef);
this.tryFolder(sharedNodeRef);
this.tryFolder(sharedParentRef);
this.executed.setFalse();
this.tryNull(null);
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNotFolder(rootNodeRef);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@IfNodeOfType(type = "cm:folder")
private void tryFolders(NodeRef... nodeRefs) {
for (NodeRef nodeRef : nodeRefs) {
QName nodeType = this.nodeService.getType(nodeRef);
Assert.isTrue(this.dictionaryService.isSubClass(nodeType, ContentModel.TYPE_FOLDER), "Unexpected node type: " + nodeType);
}
}
private void tryFolder(@IfNodeOfType(type = "cm:folder") NodeRef nodeRef) {
QName nodeType = this.nodeService.getType(nodeRef);
Assert.isTrue(this.dictionaryService.isSubClass(nodeType, ContentModel.TYPE_FOLDER), "Unexpected node type: " + nodeType);
}
private void tryFolder(@IfNodeOfType(type = "cm:folder") ChildAssociationRef childAssocRef) {
QName nodeType = this.nodeService.getType(childAssocRef.getChildRef());
Assert.isTrue(this.dictionaryService.isSubClass(nodeType, ContentModel.TYPE_FOLDER), "Unexpected node type: " + nodeType);
}
@IfNodeOfType(type = "cm:folder")
private void tryNotFolders(NodeRef... nodeRefs) {
throw new UnsupportedOperationException();
}
@IfNodeOfType
private void tryNull(NodeRef nodeRef) {
this.executed.setTrue();
}
private void tryNotFolder(@IfNodeOfType(type = "cm:folder") NodeRef nodeRef) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,41 @@
package com.inteligr8.alfresco.annotations;
import org.apache.commons.lang3.mutable.MutableBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
@Component
public class IfNotNullTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final MutableBoolean executed = new MutableBoolean();
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
this.executed.setFalse();
this.tryNotNull("test");
if (this.executed.isFalse())
throw new IllegalStateException();
this.tryNull(null);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
private void tryNotNull(@IfNotNull String str) {
this.executed.setTrue();
}
private void tryNull(@IfNotNull String str) {
throw new IllegalStateException();
}
}
@@ -0,0 +1,46 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class InvalidAuthorizableTest extends AbstractLifecycleBean implements Authorizable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AuthenticationUtil.getRunAsUser(), "An unexpected authorization: " + AuthenticationUtil.getRunAsUser());
this.tryAuthorized();
this.tryAuthorizedAsSystem();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String authorizeAsUser() {
return "!@#lkjw 1432";
}
@Authorized
private void tryAuthorized() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(this.authorizeAsUser().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + this.authorizeAsUser() + " != " + AuthenticationUtil.getRunAsUser());
}
@AuthorizedAsSystem
private void tryAuthorizedAsSystem() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
}
@@ -0,0 +1,63 @@
package com.inteligr8.alfresco.annotations;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
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) {
}
@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(100L);
} catch (InterruptedException ie) {
}
Assert.isTrue(t == threadsRun.intValue(), "The threads run unexpectedly changed: " + t + " != " + threadsRun);
threadsRun.increment();
this.logger.debug("Before end of a mutually exclusive execution block: {}", t);
}
}
@@ -0,0 +1,46 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class NoExistAuthorizableTest extends AbstractLifecycleBean implements Authorizable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AuthenticationUtil.getRunAsUser(), "An unexpected authorization: " + AuthenticationUtil.getRunAsUser());
this.tryAuthorized();
this.tryAuthorizedAsSystem();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String authorizeAsUser() {
return "doesnotexist";
}
@Authorized
private void tryAuthorized() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(this.authorizeAsUser().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + this.authorizeAsUser() + " != " + AuthenticationUtil.getRunAsUser());
}
@AuthorizedAsSystem
private void tryAuthorizedAsSystem() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
}
@@ -0,0 +1,51 @@
package com.inteligr8.alfresco.annotations;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class NullAuthorizableTest extends AbstractLifecycleBean implements Authorizable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: " + this.getClass());
Assert.isNull(AuthenticationUtil.getRunAsUser(), "An unexpected authorization: " + AuthenticationUtil.getRunAsUser());
try {
this.tryAuthorized();
throw new IllegalStateException("An 'IllegalArgumentException' was expected");
} catch (IllegalArgumentException iae) {
// suppress
}
this.tryAuthorizedAsSystem();
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Override
public String authorizeAsUser() {
return null;
}
@Authorized
private void tryAuthorized() {
throw new UnsupportedOperationException();
}
@AuthorizedAsSystem
private void tryAuthorizedAsSystem() {
Assert.hasText(AuthenticationUtil.getRunAsUser(), "An expected authorization");
Assert.isTrue(AuthenticationUtil.getSystemUserName().equals(AuthenticationUtil.getRunAsUser()), "An unexpected authorization: " + AuthenticationUtil.getSystemUserName() + " != " + AuthenticationUtil.getRunAsUser());
}
}
@@ -0,0 +1,74 @@
package com.inteligr8.alfresco.annotations;
import java.util.Random;
import org.apache.commons.lang3.mutable.MutableInt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
public class ThreadedTest extends AbstractLifecycleBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Random random = new Random();
@Override
protected void onBootstrap(ApplicationEvent event) {
this.logger.info("Running test: {}", this.getClass());
Assert.isTrue(Thread.currentThread().getId() > 0L, "An unexpected non-positive thread ID: " + Thread.currentThread().getId());
this.logger.info("Main thread: {}", Thread.currentThread().getId());
MutableInt threadCount = new MutableInt();
this.try5sync(Thread.currentThread(), threadCount);
Assert.isTrue(threadCount.intValue() == 5, "An unexpected thread count record: 5 != " + threadCount);
threadCount.setValue(0);
this.try5(Thread.currentThread(), threadCount);
Assert.isTrue(threadCount.intValue() < 5, "An unexpected thread count record: 5 >= " + threadCount);
try {
Thread.sleep(500L);
} catch (InterruptedException ie) {
// suppress
}
Assert.isTrue(threadCount.intValue() == 5, "An unexpected thread count record: 5 != " + threadCount);
}
@Override
protected void onShutdown(ApplicationEvent event) {
}
@Threaded(threads = 5)
private void try5(Thread parentThread, MutableInt threadCount) {
this.logger.info("Running inside thread: {}", Thread.currentThread().getId());
Assert.isTrue(parentThread.getId() != Thread.currentThread().getId(), "An unexpected thread ID: " + Thread.currentThread().getId());
try {
Thread.sleep(this.random.nextInt(100) + 100L);
threadCount.increment();
} catch (InterruptedException ie) {
// suppress
}
}
@Threaded(threads = 5, join = true)
private void try5sync(Thread parentThread, MutableInt threadCount) {
this.logger.info("Running inside thread: {}", Thread.currentThread().getId());
Assert.isTrue(parentThread.getId() != Thread.currentThread().getId(), "An unexpected thread ID: " + Thread.currentThread().getId());
try {
Thread.sleep(this.random.nextInt(100) + 100L);
threadCount.increment();
} catch (InterruptedException ie) {
// suppress
}
}
}
@@ -0,0 +1,11 @@
# Module debugging
log4j.logger.com.inteligr8.alfresco.annotations=trace
# WebScript debugging
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
@@ -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
@@ -0,0 +1,63 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
-->
<beans>
<!--
To support hot reloading of server side Javascript files in Share, we have to turn on development mode.
This setting will tell the Rhinoscript Processor not to compile and cache the JS files.
Cool, we can now change server side JS files and have the changes picked up,
without having to restart or refresh web scripts.
But… Due to a known bug in the Surf framework (ALF-9970) this will break the admin consoles in Share.
Override this bean and disable javascript compilation so that webscripts can be hot reloaded.
We have changed the 'compile' property from true to false.
-->
<bean id="javaScriptProcessor" class="org.alfresco.repo.jscript.RhinoScriptProcessor" init-method="register">
<property name="name">
<value>javascript</value>
</property>
<property name="extension">
<value>js</value>
</property>
<!-- Do not "compile javascript and cache compiled scripts" -->
<property name="compile">
<value>false</value>
</property>
<!-- allow sharing of sealed scopes for performance -->
<!-- disable to give each script it's own new scope which can be extended -->
<property name="shareSealedScopes">
<value>true</value>
</property>
<property name="scriptService">
<ref bean="scriptService"/>
</property>
<!-- Creates ScriptNodes which require the ServiceRegistry -->
<property name="serviceRegistry">
<ref bean="ServiceRegistry"/>
</property>
<property name="storeUrl">
<value>${spaces.store}</value>
</property>
<property name="storePath">
<value>${spaces.company_home.childname}</value>
</property>
</bean>
</beans>