refactored
This commit is contained in:
Binary file not shown.
@@ -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>
|
||||
@@ -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
@@ -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!"
|
||||
|
||||
+11
@@ -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 {
|
||||
|
||||
}
|
||||
+20
@@ -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;
|
||||
|
||||
}
|
||||
+84
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+245
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+12
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -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;
|
||||
|
||||
}
|
||||
+385
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+189
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
+24
@@ -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
|
||||
+1
@@ -0,0 +1 @@
|
||||
log4j.logger.com.inteligr8.alfresco.annotations=info
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
logger.inteligr8-annotations.name=com.inteligr8.alfresco.annotations
|
||||
logger.inteligr8-annotations.level=info
|
||||
+15
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
+111
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -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>
|
||||
Reference in New Issue
Block a user