initial checkin
This commit is contained in:
commit
85ff365031
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Maven
|
||||
target
|
||||
pom.xml.versionsBackup
|
||||
|
||||
# Eclipse
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
|
90
pom.xml
Normal file
90
pom.xml
Normal file
@ -0,0 +1,90 @@
|
||||
<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>
|
||||
|
||||
<groupId>com.inteligr8.alfresco</groupId>
|
||||
<artifactId>annotations-platform-module</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Annotations ACS Platform Module</name>
|
||||
<description>A module to support annotation-based development for Alfresco Content Services modules.</description>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://bitbucket.org/inteligr8/annotations-platform-module.git</connection>
|
||||
<developerConnection>scm:git:git@bitbucket.org:inteligr8/annotations-platform-module.git</developerConnection>
|
||||
<url>https://bitbucket.org/inteligr8/annotations-platform-module</url>
|
||||
</scm>
|
||||
<organization>
|
||||
<name>Inteligr8</name>
|
||||
<url>https://www.inteligr8.com</url>
|
||||
</organization>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>brian.long</id>
|
||||
<name>Brian Long</name>
|
||||
<email>brian@inteligr8.com</email>
|
||||
<url>https://twitter.com/brianmlong</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
|
||||
<alfresco.sdk.version>4.2.0</alfresco.sdk.version>
|
||||
<alfresco.platform.version>6.2.0-ga</alfresco.platform.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>${alfresco.platform.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Very popular, but not required, dependency -->
|
||||
<!-- Provided as an example -->
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-repository</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>io.repaint.maven</groupId>
|
||||
<artifactId>tiles-maven-plugin</artifactId>
|
||||
<version>2.26</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.0.0,2.0.0)</tile>
|
||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
|
||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.0.0,2.0.0)</tile>
|
||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile -->
|
||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.0.0,2.0.0)</tile>
|
||||
</tiles>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>alfresco-public</id>
|
||||
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
74
rad.ps1
Normal file
74
rad.ps1
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
function discoverArtifactId {
|
||||
$script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate)
|
||||
}
|
||||
|
||||
function rebuild {
|
||||
echo "Rebuilding project ..."
|
||||
mvn process-classes
|
||||
}
|
||||
|
||||
function start_ {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad process-classes
|
||||
}
|
||||
|
||||
function start_log {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad "-Ddocker.showLogs" process-classes
|
||||
}
|
||||
|
||||
function stop_ {
|
||||
discoverArtifactId
|
||||
echo "Stopping Docker containers that supported rapid application development ..."
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
echo "Stopping containers ..."
|
||||
docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*)
|
||||
echo "Removing containers ..."
|
||||
docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*)
|
||||
}
|
||||
|
||||
function tail_logs {
|
||||
param (
|
||||
$container
|
||||
)
|
||||
|
||||
discoverArtifactId
|
||||
docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container})
|
||||
}
|
||||
|
||||
function list {
|
||||
discoverArtifactId
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
}
|
||||
|
||||
switch ($args[0]) {
|
||||
"start" {
|
||||
start_
|
||||
}
|
||||
"start_log" {
|
||||
start_log
|
||||
}
|
||||
"stop" {
|
||||
stop_
|
||||
}
|
||||
"restart" {
|
||||
stop_
|
||||
start_
|
||||
}
|
||||
"rebuild" {
|
||||
rebuild
|
||||
}
|
||||
"tail" {
|
||||
tail_logs $args[1]
|
||||
}
|
||||
"containers" {
|
||||
list
|
||||
}
|
||||
default {
|
||||
echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
|
||||
}
|
||||
}
|
||||
|
||||
echo "Completed!"
|
||||
|
71
rad.sh
Normal file
71
rad.sh
Normal file
@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
discoverArtifactId() {
|
||||
ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate`
|
||||
}
|
||||
|
||||
rebuild() {
|
||||
echo "Rebuilding project ..."
|
||||
mvn process-classes
|
||||
}
|
||||
|
||||
start() {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad process-classes
|
||||
}
|
||||
|
||||
start_log() {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad -Ddocker.showLogs process-classes
|
||||
}
|
||||
|
||||
stop() {
|
||||
discoverArtifactId
|
||||
echo "Stopping Docker containers that supported rapid application development ..."
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
echo "Stopping containers ..."
|
||||
docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*`
|
||||
echo "Removing containers ..."
|
||||
docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*`
|
||||
}
|
||||
|
||||
tail_logs() {
|
||||
discoverArtifactId
|
||||
docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1`
|
||||
}
|
||||
|
||||
list() {
|
||||
discoverArtifactId
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
start_log)
|
||||
start_log
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
rebuild)
|
||||
rebuild
|
||||
;;
|
||||
tail)
|
||||
tail_logs $2
|
||||
;;
|
||||
containers)
|
||||
list
|
||||
;;
|
||||
*)
|
||||
echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo "Completed!"
|
||||
|
@ -0,0 +1,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 AspectConstrainable 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,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,12 @@
|
||||
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 Asynchronous {
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.inteligr8.alfresco.annotations;
|
||||
|
||||
public interface Authorizable {
|
||||
|
||||
String authorizeAsUser();
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
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 Authorized {
|
||||
|
||||
String value() default "";
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
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 AuthorizedAsSystem {
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
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.TYPE)
|
||||
public @interface BehaviorBean {
|
||||
|
||||
String defaultType() default "sys:base";
|
||||
|
||||
String defaultAssocType() default "";
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
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,
|
||||
ElementType.PARAMETER
|
||||
})
|
||||
public @interface IfAspect {
|
||||
|
||||
String aspect() default "";
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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,
|
||||
ElementType.PARAMETER
|
||||
})
|
||||
public @interface IfChildAssociationIsPrimary {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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,
|
||||
ElementType.PARAMETER
|
||||
})
|
||||
public @interface IfNodeExists {
|
||||
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
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,
|
||||
ElementType.PARAMETER
|
||||
})
|
||||
public @interface IfNodeType {
|
||||
|
||||
String type() default "";
|
||||
|
||||
}
|
@ -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,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 TransactionalRetryable {
|
||||
|
||||
int maxRetries() default -1;
|
||||
|
||||
int minRetryWaitInMillis() default -1;
|
||||
|
||||
int maxRetryWaitInMillis() default -1;
|
||||
|
||||
int incRetryWaitInMillis() default -1;
|
||||
|
||||
}
|
@ -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,145 @@
|
||||
package com.inteligr8.alfresco.annotations.aspect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
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.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.AspectConstrainable;
|
||||
import com.inteligr8.alfresco.annotations.IfAspect;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class AspectAspect extends QNameBasedAspect<IfAspect> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private DictionaryService dictionaryService;
|
||||
|
||||
@Autowired
|
||||
private NodeService nodeService;
|
||||
|
||||
@Value("${inteligr8.cache.aspectConstrainable.maxBeans}")
|
||||
private int maxBeans;
|
||||
|
||||
@Override
|
||||
public int getMaxBeansCache() {
|
||||
return this.maxBeans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<IfAspect> getAnnotationClass() {
|
||||
return IfAspect.class;
|
||||
}
|
||||
|
||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfAspect)")
|
||||
public void isAspectMethod() {
|
||||
}
|
||||
|
||||
@Around("isAspectMethod() or execution(* *(@com.inteligr8.alfresco.annotations.IfAspect (*), ..))")
|
||||
public void isAspect(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
this.checkParameters(joinPoint, new ApplicableParameterCallback<IfAspect>() {
|
||||
@Override
|
||||
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfAspect annotation, int parameterIndex) {
|
||||
Object arg = joinPoint.getArgs()[parameterIndex];
|
||||
Collection<NodeRef> nodeRefs = extractNodeRefs(arg);
|
||||
if (nodeRefs == null)
|
||||
return true;
|
||||
|
||||
QNameBasedCallback<IfAspect> callback = new QNameBasedCallback<IfAspect>() {
|
||||
@Override
|
||||
public boolean isConstrained() {
|
||||
return joinPoint.getThis() instanceof AspectConstrainable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstrainableClassSimpleName() {
|
||||
return AspectConstrainable.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBeanName() {
|
||||
return ((AspectConstrainable) joinPoint.getThis()).getBeanName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends QNamePattern> constrainedQNames() {
|
||||
return ((AspectConstrainable) 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(IfAspect 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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))
|
||||
nodeRefs.addAll(this.extractNodeRefs(o));
|
||||
return nodeRefs;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
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.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class AsyncAspect {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private AsyncService asyncService;
|
||||
|
||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.Asynchronous)")
|
||||
public void asyncMethod() {
|
||||
}
|
||||
|
||||
@Around("asyncMethod()")
|
||||
public void async(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
if (this.asyncService.isCurrentThreadAsynchronous()) {
|
||||
this.logger.trace("Intercepted an @Async method call while already asynchronous; executing synchronously");
|
||||
joinPoint.proceed();
|
||||
} else {
|
||||
this.logger.trace("Intercepted an @Async method call; redirecting to Async service");
|
||||
this.asyncService.push(joinPoint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
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.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.Authorized;
|
||||
import com.inteligr8.alfresco.annotations.AuthorizedAsSystem;
|
||||
import com.inteligr8.alfresco.annotations.Authorizable;
|
||||
|
||||
@Aspect
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + Short.MAX_VALUE)
|
||||
@Component
|
||||
public class AuthorizedAspect {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.Authorized || com.inteligr8.alfresco.annotations.AuthorizedAsSystem)")
|
||||
public void runAsableMethod() {
|
||||
}
|
||||
|
||||
@Around("runAsableMethod()")
|
||||
public void runAs(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
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);
|
||||
joinPoint.proceed();
|
||||
} else {
|
||||
this.logger.debug("Changing runAs context: {} => {}", currentRunAsUser, runAsUser);
|
||||
|
||||
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: {}", joinPoint.getThis().getClass(), method, runAs);
|
||||
return runAs;
|
||||
}
|
||||
|
||||
if (joinPoint.getThis() instanceof Authorizable) {
|
||||
String runAs = StringUtils.trimToNull(((Authorizable) joinPoint.getThis()).authorizeAsUser());
|
||||
this.logger.trace("The @Authorized method {}#{} is Authorizable: {}", joinPoint.getThis().getClass(), method, runAs);
|
||||
return runAs;
|
||||
}
|
||||
|
||||
Authorized runAsAnnotation = method.getAnnotation(Authorized.class);
|
||||
String runAs = StringUtils.trimToNull(runAsAnnotation.value());
|
||||
if (runAs != null) {
|
||||
this.logger.trace("The @Authorized method {}#{} must run as: {}", joinPoint.getThis().getClass(), method, runAs);
|
||||
return runAs;
|
||||
}
|
||||
|
||||
this.logger.trace("The @Authorized method {}#{} must run as system", joinPoint.getThis().getClass(), method);
|
||||
return AuthenticationUtil.getSystemUserName();
|
||||
}
|
||||
|
||||
private void 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 {
|
||||
AuthenticationUtil.runAs(work, runAsUser);
|
||||
} catch (RuntimeException re) {
|
||||
// attempt to unwrap the exception
|
||||
if (re.getMessage() != null && re.getMessage().equals("Error during run as.")) {
|
||||
throw re.getCause();
|
||||
} else {
|
||||
throw re;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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 org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary;
|
||||
|
||||
@Aspect
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100) // ordering before transaction/authorized
|
||||
@Component
|
||||
public class ChildIsPrimaryAspect extends MethodOrParameterAspect<IfChildAssociationIsPrimary> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Override
|
||||
public Class<IfChildAssociationIsPrimary> getAnnotationClass() {
|
||||
return IfChildAssociationIsPrimary.class;
|
||||
}
|
||||
|
||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary)")
|
||||
public void isChildAssocPrimaryMethod() {
|
||||
}
|
||||
|
||||
@Around("isChildAssocPrimaryMethod() or execution(* *(@com.inteligr8.alfresco.annotations.IfChildAssociationIsPrimary (*), ..))")
|
||||
public void isChildAssocPrimary(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
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,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 MethodOrParameterAspect<T extends Annotation> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
public abstract Class<T> getAnnotationClass();
|
||||
|
||||
public void 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
joinPoint.proceed();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.inteligr8.alfresco.annotations.aspect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.service.cmr.dictionary.ClassDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
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.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.IfNodeType;
|
||||
import com.inteligr8.alfresco.annotations.NodeTypeConstrainable;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class NodeTypeAspect extends QNameBasedAspect<IfNodeType> {
|
||||
|
||||
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<IfNodeType> getAnnotationClass() {
|
||||
return IfNodeType.class;
|
||||
}
|
||||
|
||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfNodeType)")
|
||||
public void isNodeTypeMethod() {
|
||||
}
|
||||
|
||||
@Around("isNodeTypeMethod() or execution(* *(@com.inteligr8.alfresco.annotations.IfNodeType (*), ..))")
|
||||
public void isNodeType(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
this.checkParameters(joinPoint, new ApplicableParameterCallback<IfNodeType>() {
|
||||
@Override
|
||||
public boolean checkParameter(ProceedingJoinPoint joinPoint, Method method, IfNodeType annotation, int parameterIndex) {
|
||||
Object arg = joinPoint.getArgs()[parameterIndex];
|
||||
Collection<NodeRef> nodeRefs = extractNodeRefs(arg);
|
||||
if (nodeRefs == null)
|
||||
return true;
|
||||
|
||||
QNameBasedCallback<IfNodeType> callback = new QNameBasedCallback<IfNodeType>() {
|
||||
@Override
|
||||
public boolean isConstrained() {
|
||||
return joinPoint.getThis() instanceof NodeTypeConstrainable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConstrainableClassSimpleName() {
|
||||
return NodeTypeConstrainable.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBeanName() {
|
||||
return ((NodeTypeConstrainable) joinPoint.getThis()).getBeanName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends QNamePattern> constrainedQNames() {
|
||||
return ((NodeTypeConstrainable) joinPoint.getThis()).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(IfNodeType 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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))
|
||||
nodeRefs.addAll(this.extractNodeRefs(o));
|
||||
return nodeRefs;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
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.Pointcut;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.IfNodeExists;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
public class OperableNodeAspect extends MethodOrParameterAspect<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)")
|
||||
public void isNodeOperableMethod() {
|
||||
}
|
||||
|
||||
@Around("isNodeOperableMethod() or execution(* *(@com.inteligr8.alfresco.annotations.IfNodeExists (*), ..))")
|
||||
public void isNodeOperable(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
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;
|
||||
|
||||
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))
|
||||
nodeRefs.addAll(this.extractNodeRefs(o));
|
||||
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,99 @@
|
||||
package com.inteligr8.alfresco.annotations.aspect;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.alfresco.repo.cache.DefaultSimpleCache;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
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;
|
||||
|
||||
public abstract class QNameBasedAspect<T extends Annotation> extends MethodOrParameterAspect<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: {}", callback.getBeanName());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
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,154 @@
|
||||
package com.inteligr8.alfresco.annotations.aspect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
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 org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.IllegalTransactionStateException;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
|
||||
|
||||
@Aspect
|
||||
@Order(Ordered.LOWEST_PRECEDENCE - Short.MAX_VALUE)
|
||||
@Component
|
||||
public class RetryingTransactionAspect {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
|
||||
public void transactionalMethod() {
|
||||
}
|
||||
|
||||
@Around("transactionalMethod()")
|
||||
public void retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
this.logger.trace("tx: {}", AlfrescoTransactionSupport.getTransactionId());
|
||||
|
||||
Method method = this.getMethod(joinPoint);
|
||||
Transactional txl = method.getAnnotation(Transactional.class);
|
||||
|
||||
if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) {
|
||||
TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class);
|
||||
|
||||
this.logger.debug("Changing TX context: {} => [ro: {}, new: {}]", AlfrescoTransactionSupport.getTransactionReadState(), txl.readOnly(), txl.propagation());
|
||||
this.execute(joinPoint, txl, txtry);
|
||||
}
|
||||
}
|
||||
|
||||
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(Transactional txl) {
|
||||
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
||||
case TXN_NONE:
|
||||
switch (txl.propagation()) {
|
||||
case NEVER:
|
||||
case NOT_SUPPORTED:
|
||||
case SUPPORTS:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
case TXN_READ_ONLY:
|
||||
return !txl.readOnly();
|
||||
case TXN_READ_WRITE:
|
||||
return txl.readOnly();
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doCreateNewTxContext(Transactional txl) {
|
||||
switch (txl.propagation()) {
|
||||
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");
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
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 UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private void execute(final ProceedingJoinPoint joinPoint, Transactional txl, TransactionalRetryable txtry) throws Throwable {
|
||||
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
|
||||
@Override
|
||||
public Object execute() throws Throwable {
|
||||
logger.trace("tx: {}", AlfrescoTransactionSupport.getTransactionId());
|
||||
|
||||
try {
|
||||
return joinPoint.proceed();
|
||||
} catch (Exception | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("This should never happen", t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RetryingTransactionHelper rthelper = new RetryingTransactionHelper();
|
||||
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.timeout() > 0)
|
||||
rthelper.setMaxExecutionMs(txl.timeout() * 1000L);
|
||||
|
||||
try {
|
||||
rthelper.doInTransaction(rtcallback, txl.readOnly(), txl.propagation().equals(Propagation.REQUIRES_NEW));
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.inteligr8.alfresco.annotations.job;
|
||||
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
|
||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
||||
|
||||
public class AsyncJob implements Job {
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
AsyncService asyncService = (AsyncService) context.getMergedJobDataMap().get("asyncService");
|
||||
if (asyncService == null)
|
||||
throw new JobExecutionException("An 'asyncService' object is required in the job map");
|
||||
|
||||
try {
|
||||
asyncService.poll();
|
||||
} catch (AsyncProcessException ape) {
|
||||
throw new JobExecutionException(ape, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.inteligr8.alfresco.annotations.service;
|
||||
|
||||
public class AsyncProcessException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 8254359296736253436L;
|
||||
|
||||
public AsyncProcessException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.inteligr8.alfresco.annotations.service;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
|
||||
/**
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
public interface AsyncService {
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isCurrentThreadAsynchronous();
|
||||
|
||||
void poll() throws AsyncProcessException;
|
||||
|
||||
void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException;
|
||||
|
||||
}
|
@ -0,0 +1,538 @@
|
||||
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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jms.Connection;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.MessageConsumer;
|
||||
import javax.jms.MessageProducer;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.Session;
|
||||
import javax.transaction.TransactionManager;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.cache.SimpleCache;
|
||||
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.apache.activemq.ActiveMQXAConnectionFactory;
|
||||
import org.apache.activemq.jms.pool.XaPooledConnectionFactory;
|
||||
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.impl.JobDetailImpl;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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 org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.inteligr8.alfresco.annotations.AuthorizedAsSystem;
|
||||
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
|
||||
import com.inteligr8.alfresco.annotations.job.AsyncJob;
|
||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
||||
|
||||
/**
|
||||
* This class provides integration with MQ for the asynchronous filing of nodes.
|
||||
*
|
||||
* @author brian@inteligr8.com
|
||||
*/
|
||||
@Component
|
||||
public class MqAsyncService extends AbstractLifecycleBean implements AsyncService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)");
|
||||
private final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Value("${inteligr8.async.mq.enabled}")
|
||||
protected boolean enabled;
|
||||
|
||||
@Value("${inteligr8.async.mq.url:#{null}}")
|
||||
protected String url;
|
||||
|
||||
@Value("${inteligr8.async.mq.username:#{null}}")
|
||||
protected String username;
|
||||
|
||||
@Value("${inteligr8.async.mq.password:#{null}}")
|
||||
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;
|
||||
|
||||
@Autowired
|
||||
protected TransactionManager txManager;
|
||||
|
||||
private XaPooledConnectionFactory factory;
|
||||
|
||||
private SimpleCache<Pair<Class<?>, String>, Method> methodCache;
|
||||
|
||||
private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() {
|
||||
@Override
|
||||
public Boolean get() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event) {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
|
||||
ActiveMQXAConnectionFactory factory = new ActiveMQXAConnectionFactory(this.url);
|
||||
|
||||
XaPooledConnectionFactory pool = new XaPooledConnectionFactory();
|
||||
pool.setConnectionFactory(factory);
|
||||
pool.setMaxConnections(this.maxConnections);
|
||||
pool.setTransactionManager(this.txManager);
|
||||
pool.start();
|
||||
|
||||
this.factory = pool;
|
||||
|
||||
JobKey jobKey = new JobKey("behaviour-async", "inteligr8-annotations");
|
||||
|
||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||
jobDetail.setKey(jobKey);
|
||||
jobDetail.setRequestsRecovery(true);
|
||||
jobDetail.setJobClass(AsyncJob.class);
|
||||
jobDetail.getJobDataMap().put("asyncService", this);
|
||||
|
||||
try {
|
||||
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||
scheduler.addJob(jobDetail, false);
|
||||
scheduler.triggerJob(jobKey);
|
||||
} catch (SchedulerException se) {
|
||||
this.logger.error("The behavior policy async service failed to start; no asynchronous policies will be processed!", se);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event) {
|
||||
this.factory.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCurrentThreadAsynchronous() {
|
||||
return this.isAsync.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void poll() throws AsyncProcessException {
|
||||
this.logger.trace("poll()");
|
||||
this.isAsync.set(true);
|
||||
|
||||
try {
|
||||
Connection mqcon = this.factory.createConnection(this.username, this.password);
|
||||
try {
|
||||
mqcon.setClientID(this.clientId);
|
||||
|
||||
Session mqsession = mqcon.createSession(true, Session.CLIENT_ACKNOWLEDGE);
|
||||
try {
|
||||
this.logger.debug("Polling messages for asynchronous policy execution");
|
||||
this.pollErrors(mqsession);
|
||||
this.pollMain(mqsession);
|
||||
} finally {
|
||||
mqsession.close();
|
||||
}
|
||||
} finally {
|
||||
mqcon.close();
|
||||
}
|
||||
} catch (JMSException je) {
|
||||
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
||||
}
|
||||
}
|
||||
|
||||
private void pollErrors(Session mqsession) throws JMSException {
|
||||
this.logger.debug("Polling previously errored messages");
|
||||
|
||||
Queue mqqueue = mqsession.createQueue(this.errorQueueName);
|
||||
Set<String> msgIds = new HashSet<>();
|
||||
int ackd = 0;
|
||||
|
||||
MessageConsumer consumer = mqsession.createConsumer(mqqueue);
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
Boolean processed = this.pollTx(mqsession, consumer, msgIds);
|
||||
if (processed == null) {
|
||||
break;
|
||||
} else if (processed.booleanValue()) {
|
||||
ackd++;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
consumer.close();
|
||||
}
|
||||
|
||||
this.logger.info("Successfully processed {} of {} previously errored messages", ackd, msgIds.size());
|
||||
}
|
||||
|
||||
private void pollMain(Session mqsession) throws JMSException {
|
||||
this.logger.debug("Polling ongoing messages ...");
|
||||
|
||||
Queue mqqueue = mqsession.createQueue(this.queueName);
|
||||
|
||||
MessageConsumer consumer = mqsession.createConsumer(mqqueue);
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
this.pollTx(mqsession, consumer, null);
|
||||
}
|
||||
} finally {
|
||||
consumer.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
@TransactionalRetryable(maxRetries = 3)
|
||||
@AuthorizedAsSystem
|
||||
private Boolean pollTx(Session mqsession, MessageConsumer consumer, Set<String> msgIds) throws JMSException {
|
||||
Message mqmsg = consumer.receive();
|
||||
|
||||
if (msgIds != null && !msgIds.add(mqmsg.getJMSMessageID())) {
|
||||
this.logger.debug("Received a message again; assuming we have (re)tried all errored messages: {}", mqmsg.getJMSMessageID());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.processIncomingMessage(mqsession, mqmsg, msgIds != null)) {
|
||||
mqmsg.acknowledge();
|
||||
return true;
|
||||
}
|
||||
} catch (RuntimeException | Error e) {
|
||||
this.logger.error("An unexpected issue occurred", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processIncomingMessage(Session mqsession, Message mqmsg, boolean isErrorQueue) throws JMSException {
|
||||
String msgId = mqmsg.getJMSMessageID();
|
||||
this.logger.debug("Received message: {}", msgId);
|
||||
|
||||
String type = mqmsg.getJMSType();
|
||||
Matcher matcher = this.typePattern.matcher(type);
|
||||
if (!matcher.find()) {
|
||||
this.logger.warn("The queue has a message ('{}') with an unsupported JMS type: {}", msgId, type);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> beanClass = Class.forName(matcher.group(2));
|
||||
this.logger.trace("Preparing to execute using bean type: {}", beanClass);
|
||||
Object bean = this.getApplicationContext().getBean(beanClass);
|
||||
this.logger.trace("Found qualifying bean: {}", bean);
|
||||
|
||||
String methodName = matcher.group(3);
|
||||
Method method = this.findMethod(beanClass, methodName);
|
||||
this.logger.trace("Found qualifying method: {}", method);
|
||||
Parameter[] params = method.getParameters();
|
||||
|
||||
Object[] args = new Object[params.length];
|
||||
|
||||
for (int a = 0; a < args.length; a++) {
|
||||
Object arg = mqmsg.getObjectProperty("arg" + a);
|
||||
if (arg == null)
|
||||
continue;
|
||||
|
||||
args[a] = this.unmarshal(params[a], arg);
|
||||
}
|
||||
|
||||
switch (method.getName()) {
|
||||
case "onLoadDynamicModel":
|
||||
args[1] = args[0];
|
||||
args[0] = this.loadModel((NodeRef) args[1]);
|
||||
}
|
||||
|
||||
method.invoke(bean, args);
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
this.logger.error("A policy bean could not be found; will try on next restart");
|
||||
this.logger.error("The bean '{}' could not be found: {}", matcher.group(2), cnfe.getMessage());
|
||||
if (isErrorQueue)
|
||||
return false;
|
||||
this.moveToErrorQueue(mqsession, mqmsg);
|
||||
} catch (IOException ie) {
|
||||
this.logger.warn("This should never happen: " + ie.getMessage());
|
||||
// return to queue and retry indefinitely
|
||||
return false;
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
this.logger.error("A policy enumeration argument could not be constructed; will try on next restart");
|
||||
this.logger.error("An argument could not be The bean '{}' could not be found: {}", matcher.group(2), nsme.getMessage());
|
||||
if (isErrorQueue)
|
||||
return false;
|
||||
this.moveToErrorQueue(mqsession, mqmsg);
|
||||
} catch (IllegalAccessException iae) {
|
||||
this.logger.error("A policy method was not accessible (public); will try on next restart");
|
||||
this.logger.warn("The bean '{}' method '{}' is not accessible: {}", matcher.group(2), matcher.group(3), iae.getMessage());
|
||||
if (isErrorQueue)
|
||||
return false;
|
||||
this.moveToErrorQueue(mqsession, mqmsg);
|
||||
} catch (InstantiationException | InvocationTargetException ie) {
|
||||
this.logger.error("A policy method execution failed; will try on next restart");
|
||||
this.logger.warn("The bean '{}' method '{}' execution failed: {}", matcher.group(2), matcher.group(3), ie.getMessage());
|
||||
if (isErrorQueue)
|
||||
return false;
|
||||
this.moveToErrorQueue(mqsession, mqmsg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void moveToErrorQueue(Session mqsession, Message mqmsg) throws JMSException {
|
||||
Queue mqqueue = mqsession.createQueue(this.errorQueueName);
|
||||
|
||||
MessageProducer producer = mqsession.createProducer(mqqueue);
|
||||
try {
|
||||
producer.send(mqmsg);
|
||||
} finally {
|
||||
producer.close();
|
||||
}
|
||||
}
|
||||
|
||||
private 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");
|
||||
|
||||
Class<?> beanType = joinPoint.getThis().getClass();
|
||||
this.logger.debug("Queuing for bean: {}", beanType);
|
||||
|
||||
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = methodSig.getMethod();
|
||||
this.logger.debug("Queuing for method: {}", method);
|
||||
|
||||
this.push(beanType, method.getName(), Arrays.asList(joinPoint.getArgs()));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException {
|
||||
this.logger.trace("push({}, {}, {})", callbackBean, callbackMethod, args);
|
||||
|
||||
UUID msgId = UUID.randomUUID();
|
||||
|
||||
try {
|
||||
Connection mqcon = this.factory.createConnection(this.username, this.password);
|
||||
try {
|
||||
mqcon.setClientID(this.clientId);
|
||||
|
||||
Session mqsession = mqcon.createSession(true, Session.AUTO_ACKNOWLEDGE);
|
||||
try {
|
||||
this.logger.trace("Sending policy as message: {} => {}", callbackMethod, msgId);
|
||||
|
||||
Queue mqqueue = mqsession.createQueue(this.queueName);
|
||||
|
||||
Message mqmsg = mqsession.createMessage();
|
||||
mqmsg.setJMSMessageID(msgId.toString());
|
||||
mqmsg.setJMSType("v1:" + callbackBean.getClass() + "#" + callbackMethod);
|
||||
|
||||
int i = 0;
|
||||
for (Object arg : args)
|
||||
mqmsg.setObjectProperty("arg" + (i++), this.marshal(arg));
|
||||
|
||||
MessageProducer producer = mqsession.createProducer(mqqueue);
|
||||
try {
|
||||
producer.send(mqmsg);
|
||||
} finally {
|
||||
producer.close();
|
||||
}
|
||||
|
||||
this.logger.debug("Sent node as message: {} => {}", callbackMethod, msgId);
|
||||
mqsession.commit();
|
||||
} finally {
|
||||
mqsession.close();
|
||||
}
|
||||
} finally {
|
||||
mqcon.close();
|
||||
}
|
||||
} catch (JMSException je) {
|
||||
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
Class<?> paramType = param.getType();
|
||||
this.logger.trace("Unmarshaling parameter of type: {}", paramType);
|
||||
|
||||
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);
|
||||
Constructor<?> cons = paramType.getConstructor(String.class);
|
||||
return cons.newInstance(arg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private Object marshal(Object arg) {
|
||||
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 {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
private M2Model loadModel(NodeRef nodeRef) throws IOException {
|
||||
ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
||||
InputStream istream = creader.getContentInputStream();
|
||||
try {
|
||||
return M2Model.createModel(istream);
|
||||
} finally {
|
||||
istream.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
|
||||
inteligr8.async.mq.enabled=false
|
||||
inteligr8.async.mq.url=${messaging.broker.url}
|
||||
inteligr8.async.mq.username=${messaging.broker.username}
|
||||
inteligr8.async.mq.password=${messaging.broker.password}
|
||||
inteligr8.async.mq.queuePrefix=inteligr8.acs.
|
||||
inteligr8.async.mq.clientId=acs
|
||||
inteligr8.async.mq.pool.max=5
|
||||
|
||||
inteligr8.cache.nodeTypeConstrainable.maxBeans=32
|
@ -0,0 +1 @@
|
||||
log4j.logger.com.inteligr8.alfresco.annotations=info
|
@ -0,0 +1,13 @@
|
||||
<?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"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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,4 @@
|
||||
module.id=${project.artifactId}
|
||||
module.title=${project.name}
|
||||
module.description=${project.description}
|
||||
module.version=${project.version}
|
@ -0,0 +1,8 @@
|
||||
# Module debugging
|
||||
log4j.logger.com.inteligr8.alfresco.foldering=trace
|
||||
|
||||
# WebScript debugging
|
||||
log4j.logger.org.springframework.extensions.webscripts.ScriptLogger=debug
|
||||
|
||||
# non-WebScript JavaScript execution debugging
|
||||
log4j.logger.org.alfresco.repo.jscript.ScriptLogger=debug
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user