Compare commits
	
		
			8 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 74635514fe | |||
| 6e7b5311f1 | |||
| a07079f17d | |||
| fd8e1535d5 | |||
| d679d7efcc | |||
| 38b9f6e35f | |||
| 62da2b3830 | |||
| 240adf865d | 
							
								
								
									
										15
									
								
								core/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								core/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | <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> | ||||||
|  | </project> | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Repeatable; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This annotation tells the framework to only execute the annotated method if | ||||||
|  |  * the ACS authenticated user is authorized. | ||||||
|  |  *  | ||||||
|  |  * @see com.inteligr8.alfresco.annotations.Authorizable | ||||||
|  |  */ | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | @Target(ElementType.METHOD) | ||||||
|  | @Repeatable(IfAuthorizeds.class) | ||||||
|  | public @interface IfAuthorized { | ||||||
|  | 	 | ||||||
|  | 	/** | ||||||
|  | 	 * The authorities (users and/or user groups) to constrain for the | ||||||
|  | 	 * authorization context.  Only one authority needs to match.  To | ||||||
|  | 	 * require multiple authorities, use multiple @IfAuthorized | ||||||
|  | 	 * annotations. | ||||||
|  | 	 *  | ||||||
|  | 	 * @return An array of ACS authorities; empty means any authenticated user. | ||||||
|  | 	 */ | ||||||
|  | 	String[] value() default ""; | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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 IfAuthorizeds { | ||||||
|  | 	 | ||||||
|  | 	IfAuthorized[] value(); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| package com.inteligr8.alfresco.annotations.aspect; | package com.inteligr8.alfresco.annotations.aspect; | ||||||
| 
 | 
 | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
| import java.lang.reflect.Method; | import java.lang.reflect.Method; | ||||||
| 
 | 
 | ||||||
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport; | import org.alfresco.repo.transaction.AlfrescoTransactionSupport; | ||||||
| @@ -8,18 +9,18 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; | |||||||
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; | ||||||
| import org.alfresco.service.transaction.TransactionService; | import org.alfresco.service.transaction.TransactionService; | ||||||
| import org.aspectj.lang.ProceedingJoinPoint; | 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.aspectj.lang.reflect.MethodSignature; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.context.ApplicationContext; | ||||||
| import org.springframework.transaction.IllegalTransactionStateException; | import org.springframework.transaction.IllegalTransactionStateException; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
| 
 | 
 | ||||||
| import com.inteligr8.alfresco.annotations.TransactionalRetryable; | import com.inteligr8.alfresco.annotations.TransactionalRetryable; | ||||||
| import com.inteligr8.alfresco.annotations.TransactionalWrapper; | 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 |  * This aspect implements the @Transactional and @TransactionalRetryable | ||||||
| @@ -37,33 +38,41 @@ import com.inteligr8.alfresco.annotations.TransactionalWrapper; | |||||||
|  * @see org.springframework.transaction.annotation.Transactional |  * @see org.springframework.transaction.annotation.Transactional | ||||||
|  * @see com.inteligr8.alfresco.annotations.TransactionalRetryable |  * @see com.inteligr8.alfresco.annotations.TransactionalRetryable | ||||||
|  */ |  */ | ||||||
| @Aspect | public abstract class AbstractRetryingTransactionAspect { | ||||||
| @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") |  | ||||||
| public class RetryingTransactionAspect { |  | ||||||
| 	 | 	 | ||||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
| 	 | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private ApplicationContext context; | ||||||
|  | 	 | ||||||
| 	@Autowired | 	@Autowired | ||||||
| 	private TransactionService txService; | 	private TransactionService txService; | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | 	public abstract String getJtaInterfaceName(); | ||||||
| 	public void isTransactionalAnnotated() { |  | ||||||
| 	} |  | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | 	/** | ||||||
| 	public void isJtaTransactionalAnnotated() { | 	 * A @Pointcunt annotation is not recognized in super classes. | ||||||
| 	} | 	 */ | ||||||
|  | 	//@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | ||||||
|  | 	public abstract void isTransactionalAnnotated(); | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | 	public abstract void isJtaTransactionalAnnotated(); | ||||||
| 	public void isTransactionalRetryableAnnotated() { |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()") | 	/** | ||||||
|  | 	 * A @Pointcunt annotation is not recognized in super classes. | ||||||
|  | 	 */ | ||||||
|  | 	//@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | ||||||
|  | 	public abstract void isTransactionalRetryableAnnotated(); | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * An @Around annotation is not recognized in super classes. | ||||||
|  | 	 */ | ||||||
|  | 	//@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()") | ||||||
| 	public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable { | 	public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||
| 		this.logger.trace("retryingTransactional({})", joinPoint); | 		this.logger.trace("retryingTransactional({})", joinPoint); | ||||||
| 		 | 		 | ||||||
| 		Method method = this.getMethod(joinPoint); | 		Method method = this.getMethod(joinPoint); | ||||||
| 		TransactionalWrapper txl = TransactionalWrapper.wrap(method); | 		TransactionalAnnotationAdapter txl = this.wrapTransactionalAnnotation(method); | ||||||
| 		TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class); | 		TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class); | ||||||
| 		 | 		 | ||||||
| 		if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) { | 		if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) { | ||||||
| @@ -77,6 +86,29 @@ public class RetryingTransactionAspect { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
|  | 	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) { | 	private Method getMethod(ProceedingJoinPoint joinPoint) { | ||||||
| 		if (!(joinPoint.getSignature() instanceof MethodSignature)) | 		if (!(joinPoint.getSignature() instanceof MethodSignature)) | ||||||
| 			throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods"); | 			throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods"); | ||||||
| @@ -85,7 +117,7 @@ public class RetryingTransactionAspect { | |||||||
| 		return methodSig.getMethod(); | 		return methodSig.getMethod(); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private boolean isReadStateChange(TransactionalWrapper txl) { | 	private boolean isReadStateChange(TransactionalAnnotationAdapter txl) { | ||||||
| 		if (txl == null) | 		if (txl == null) | ||||||
| 			return false; | 			return false; | ||||||
| 		 | 		 | ||||||
| @@ -114,7 +146,7 @@ public class RetryingTransactionAspect { | |||||||
| 		return txtry != null; | 		return txtry != null; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private boolean doCreateNewTxContext(TransactionalWrapper txl) { | 	private boolean doCreateNewTxContext(TransactionalAnnotationAdapter txl) { | ||||||
| 		if (txl == null) { | 		if (txl == null) { | ||||||
| 			return false; | 			return false; | ||||||
| 		} else switch (txl.getPropagation()) { | 		} else switch (txl.getPropagation()) { | ||||||
| @@ -136,6 +168,7 @@ public class RetryingTransactionAspect { | |||||||
| 						if (txl.isReadOnly()) | 						if (txl.isReadOnly()) | ||||||
| 							throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory"); | 							throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory"); | ||||||
| 				} | 				} | ||||||
|  | 				return false; | ||||||
| 			case NOT_SUPPORTED: | 			case NOT_SUPPORTED: | ||||||
| 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | ||||||
| 					case TXN_NONE: | 					case TXN_NONE: | ||||||
| @@ -159,7 +192,7 @@ public class RetryingTransactionAspect { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private Object execute(final ProceedingJoinPoint joinPoint, TransactionalWrapper txl, TransactionalRetryable txtry) throws Throwable { | 	private Object execute(final ProceedingJoinPoint joinPoint, TransactionalAnnotationAdapter txl, TransactionalRetryable txtry) throws Throwable { | ||||||
| 		RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() { | 		RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() { | ||||||
| 			@Override | 			@Override | ||||||
| 			public Object execute() throws Throwable { | 			public Object execute() throws Throwable { | ||||||
| @@ -0,0 +1,92 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.aspect; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.util.Set; | ||||||
|  |  | ||||||
|  | import org.alfresco.repo.security.authentication.AuthenticationUtil; | ||||||
|  | import org.alfresco.util.collections.CollectionUtils; | ||||||
|  | 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.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.extensions.webscripts.Description.RequiredAuthentication; | ||||||
|  | import org.springframework.extensions.webscripts.WebScriptException; | ||||||
|  | import org.springframework.http.HttpStatus; | ||||||
|  | import org.springframework.web.context.WebApplicationContext; | ||||||
|  | import org.springframework.web.server.ResponseStatusException; | ||||||
|  |  | ||||||
|  | import com.inteligr8.alfresco.annotations.IfAuthorized; | ||||||
|  | import com.inteligr8.alfresco.annotations.context.WebScriptContext; | ||||||
|  |  | ||||||
|  | import net.sf.acegisecurity.GrantedAuthority; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This aspect implements the IfAuthorized annotation. | ||||||
|  |  *  | ||||||
|  |  * @see com.inteligr8.alfresco.annotations.IfAuthorized | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | public class IfAuthorizedAspect { | ||||||
|  | 	 | ||||||
|  | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
|  | 	 | ||||||
|  | 	@Autowired(required = false) | ||||||
|  | 	private WebScriptContext wscontext; | ||||||
|  | 	 | ||||||
|  | 	@Autowired(required = false) | ||||||
|  | 	private WebApplicationContext wacontext; | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfAuthorized) && execution(* *(..))") | ||||||
|  | 	public void isIfAuthorizedAnnotated() { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Around("isIfAuthorizedAnnotated()") | ||||||
|  | 	public Object ifAuthorized(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||
|  | 		this.logger.trace("ifAuthorized({})", joinPoint); | ||||||
|  | 		 | ||||||
|  | 		if (this.wscontext != null && !RequiredAuthentication.user.equals(this.wscontext.getWebscript().getDescription().getRequiredAuthentication())) | ||||||
|  | 			return joinPoint.proceed(); | ||||||
|  | 		 | ||||||
|  | 		if (!(joinPoint.getSignature() instanceof MethodSignature)) | ||||||
|  | 			throw new IllegalStateException("The @IfAuthorized annotation must be on methods and methods have signatures"); | ||||||
|  | 		 | ||||||
|  | 		MethodSignature methodSig = (MethodSignature) joinPoint.getSignature(); | ||||||
|  | 		Method method = methodSig.getMethod(); | ||||||
|  | 		IfAuthorized[] ifauths = method.getAnnotationsByType(IfAuthorized.class); | ||||||
|  | 		for (IfAuthorized ifauth : ifauths) { | ||||||
|  | 			if (ifauth.value() != null && ifauth.value().length > 0) { | ||||||
|  | 				if (!this.isAuthorized(ifauth.value())) | ||||||
|  | 					return this.unauthorized(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return joinPoint.proceed(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	protected boolean isAuthorized(String[] permittedAuthorities) { | ||||||
|  | 		Set<String> permittedAuthoritiesSet = CollectionUtils.asSet(permittedAuthorities); | ||||||
|  | 		 | ||||||
|  | 		GrantedAuthority[] authenticatedAuthorities = AuthenticationUtil.getFullAuthentication().getAuthorities(); | ||||||
|  | 		for (GrantedAuthority auth : authenticatedAuthorities) { | ||||||
|  | 			if (permittedAuthoritiesSet.contains(auth.getAuthority())) | ||||||
|  | 				return true; | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	protected Object unauthorized() { | ||||||
|  | 		if (this.wscontext != null) { | ||||||
|  | 			throw new WebScriptException(HttpStatus.FORBIDDEN.value(), "The authenticated user is not authorized to use this resource"); | ||||||
|  | 		} else if (this.wacontext != null) { | ||||||
|  | 			throw new ResponseStatusException(HttpStatus.FORBIDDEN, "The authenticated user is not authorized to use this resource"); | ||||||
|  | 		} else { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -8,8 +8,6 @@ import java.util.HashSet; | |||||||
| import java.util.LinkedHashSet; | import java.util.LinkedHashSet; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.PostConstruct; |  | ||||||
| 
 |  | ||||||
| import org.alfresco.repo.cache.DefaultSimpleCache; | import org.alfresco.repo.cache.DefaultSimpleCache; | ||||||
| import org.alfresco.repo.cache.SimpleCache; | import org.alfresco.repo.cache.SimpleCache; | ||||||
| import org.alfresco.service.cmr.repository.AssociationRef; | import org.alfresco.service.cmr.repository.AssociationRef; | ||||||
| @@ -23,6 +21,8 @@ import org.slf4j.Logger; | |||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||||
| 
 | 
 | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | 
 | ||||||
| public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> { | public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> { | ||||||
| 	 | 	 | ||||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | 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.context.annotation.Bean; | ||||||
|  | import org.springframework.context.annotation.Scope; | ||||||
|  | import org.springframework.extensions.webscripts.WebScript; | ||||||
|  | import org.springframework.extensions.webscripts.WebScriptRequest; | ||||||
|  | import org.springframework.extensions.webscripts.WebScriptResponse; | ||||||
|  | import org.springframework.web.context.WebApplicationContext; | ||||||
|  |  | ||||||
|  | import com.inteligr8.alfresco.annotations.context.WebScriptContext; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This aspect captures the WebScript execution context. | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | public class WebScriptAspect { | ||||||
|  | 	 | ||||||
|  | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
|  | 	 | ||||||
|  | 	private ThreadLocal<WebScriptContext> context = new ThreadLocal<>(); | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("execution(public void org.springframework.extensions.webscripts.WebScript.execute(..))") | ||||||
|  | 	public void isWebScriptExecute() { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Around("isWebScriptExecute()") | ||||||
|  | 	public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||
|  | 		this.logger.trace("execute({})", joinPoint); | ||||||
|  |  | ||||||
|  | 		WebScript ws = (WebScript) joinPoint.getTarget(); | ||||||
|  | 		WebScriptRequest req = (WebScriptRequest) joinPoint.getArgs()[0]; | ||||||
|  | 		WebScriptResponse res = (WebScriptResponse) joinPoint.getArgs()[1]; | ||||||
|  | 		this.context.set(new WebScriptContext(ws, req, res)); | ||||||
|  | 		 | ||||||
|  | 		return joinPoint.proceed(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Bean | ||||||
|  | 	@Scope(WebApplicationContext.SCOPE_REQUEST) | ||||||
|  | 	public WebScriptContext getContext() { | ||||||
|  | 		return this.context.get(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,31 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.context; | ||||||
|  |  | ||||||
|  | import org.springframework.extensions.webscripts.WebScript; | ||||||
|  | import org.springframework.extensions.webscripts.WebScriptRequest; | ||||||
|  | import org.springframework.extensions.webscripts.WebScriptResponse; | ||||||
|  |  | ||||||
|  | public class WebScriptContext { | ||||||
|  | 	 | ||||||
|  | 	private final WebScript webscript; | ||||||
|  | 	private final WebScriptRequest request; | ||||||
|  | 	private final WebScriptResponse response; | ||||||
|  | 	 | ||||||
|  | 	public WebScriptContext(WebScript webscript, WebScriptRequest request, WebScriptResponse response) { | ||||||
|  | 		this.webscript = webscript; | ||||||
|  | 		this.request = request; | ||||||
|  | 		this.response = response; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public WebScript getWebscript() { | ||||||
|  | 		return webscript; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public WebScriptRequest getRequest() { | ||||||
|  | 		return request; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public WebScriptResponse getResponse() { | ||||||
|  | 		return response; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -22,24 +22,13 @@ import java.util.ArrayList; | |||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Map.Entry; | import java.util.Map.Entry; | ||||||
| import java.util.Set; |  | ||||||
| import java.util.UUID; |  | ||||||
| import java.util.function.Supplier; | import java.util.function.Supplier; | ||||||
| import java.util.regex.Matcher; |  | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
| 
 | 
 | ||||||
| import javax.jms.Connection; | import org.alfresco.error.AlfrescoRuntimeException; | ||||||
| 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 org.alfresco.model.ContentModel; | import org.alfresco.model.ContentModel; | ||||||
| import org.alfresco.repo.cache.SimpleCache; | import org.alfresco.repo.cache.SimpleCache; | ||||||
| import org.alfresco.repo.dictionary.M2Model; | import org.alfresco.repo.dictionary.M2Model; | ||||||
| @@ -56,8 +45,6 @@ import org.alfresco.service.namespace.NamespaceService; | |||||||
| import org.alfresco.service.namespace.QName; | import org.alfresco.service.namespace.QName; | ||||||
| import org.alfresco.service.transaction.TransactionService; | import org.alfresco.service.transaction.TransactionService; | ||||||
| import org.alfresco.util.Pair; | import org.alfresco.util.Pair; | ||||||
| import org.apache.activemq.ActiveMQConnectionFactory; |  | ||||||
| import org.apache.activemq.jms.pool.PooledConnectionFactory; |  | ||||||
| import org.aspectj.lang.ProceedingJoinPoint; | import org.aspectj.lang.ProceedingJoinPoint; | ||||||
| import org.aspectj.lang.reflect.MethodSignature; | import org.aspectj.lang.reflect.MethodSignature; | ||||||
| import org.quartz.JobKey; | import org.quartz.JobKey; | ||||||
| @@ -69,35 +56,30 @@ import org.quartz.impl.JobDetailImpl; | |||||||
| import org.quartz.impl.StdSchedulerFactory; | import org.quartz.impl.StdSchedulerFactory; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | 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.Autowired; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.context.ApplicationEvent; | import org.springframework.context.ApplicationEvent; | ||||||
| import org.springframework.extensions.surf.util.AbstractLifecycleBean; | 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.fasterxml.jackson.databind.ObjectMapper; | ||||||
| import com.inteligr8.alfresco.annotations.AuthorizedAsSystem; |  | ||||||
| import com.inteligr8.alfresco.annotations.Threadable; | import com.inteligr8.alfresco.annotations.Threadable; | ||||||
| import com.inteligr8.alfresco.annotations.Threaded; |  | ||||||
| import com.inteligr8.alfresco.annotations.TransactionalRetryable; |  | ||||||
| import com.inteligr8.alfresco.annotations.job.AsyncJob; | import com.inteligr8.alfresco.annotations.job.AsyncJob; | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncService; | import com.inteligr8.alfresco.annotations.service.AsyncService; | ||||||
| 
 | 
 | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | import jakarta.annotation.PreDestroy; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * This class provides integration with MQ for the asynchronous method executions. |  | ||||||
|  *  |  | ||||||
|  * @author brian@inteligr8.com |  * @author brian@inteligr8.com | ||||||
|  */ |  */ | ||||||
| @Component("async.mq") | public abstract class AbstractMqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable { | ||||||
| public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, Threadable { |  | ||||||
| 	 | 	 | ||||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
| 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | ||||||
| 	private final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); | 	protected final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); | ||||||
| 	private final ObjectMapper om = new ObjectMapper(); | 	protected final ObjectMapper om = new ObjectMapper(); | ||||||
|      |      | ||||||
| 	@Value("${inteligr8.async.mq.enabled}") | 	@Value("${inteligr8.async.mq.enabled}") | ||||||
| 	protected boolean enabled; | 	protected boolean enabled; | ||||||
| @@ -144,13 +126,11 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     @Autowired |     @Autowired | ||||||
|     protected TransactionService txService; |     protected TransactionService txService; | ||||||
|      |      | ||||||
|     private String hostname; |     protected String hostname; | ||||||
|      |      | ||||||
|     private PooledConnectionFactory factory; |     protected SimpleCache<Pair<Class<?>, String>, Method> methodCache; | ||||||
|      |      | ||||||
|     private SimpleCache<Pair<Class<?>, String>, Method> methodCache; |     protected ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() { | ||||||
|      |  | ||||||
|     private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() { |  | ||||||
|     	@Override |     	@Override | ||||||
|     	public Boolean get() { |     	public Boolean get() { | ||||||
|     		return false; |     		return false; | ||||||
| @@ -158,7 +138,20 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 	}); | 	}); | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     protected void onBootstrap(ApplicationEvent event) { |     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) |     	if (!this.enabled) | ||||||
|     		return; |     		return; | ||||||
|     	 |     	 | ||||||
| @@ -168,16 +161,17 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     		this.hostname = "unknown"; |     		this.hostname = "unknown"; | ||||||
|     	} |     	} | ||||||
| 
 | 
 | ||||||
|     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); |  | ||||||
|     	 |  | ||||||
|     	PooledConnectionFactory pool = new PooledConnectionFactory(); |  | ||||||
|     	pool.setConnectionFactory(factory); |  | ||||||
|     	pool.setMaxConnections(this.maxConnections); |  | ||||||
|     	pool.start(); |  | ||||||
|     	 |  | ||||||
|     	this.factory = pool; |  | ||||||
| 
 |  | ||||||
| 		if (this.workerThreads <= 0) | 		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; |     		return; | ||||||
|     	 |     	 | ||||||
|     	JobDetailImpl jobDetail = new JobDetailImpl(); |     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||||
| @@ -206,14 +200,11 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	} catch (SchedulerException se) { |     	} catch (SchedulerException se) { | ||||||
|     		this.logger.warn("The MQ async service job failed to stop", se); |     		this.logger.warn("The MQ async service job failed to stop", se); | ||||||
|     	} |     	} | ||||||
|     	 |  | ||||||
|     	if (this.factory != null) |  | ||||||
|     		this.factory.stop(); |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     public boolean isEnabled() { |     public boolean isEnabled() { | ||||||
| 		return enabled; | 		return this.enabled; | ||||||
| 	} | 	} | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
| @@ -226,183 +217,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 		return this.isAsync.get(); | 		return this.isAsync.get(); | ||||||
| 	} | 	} | ||||||
|      |      | ||||||
|     @Override |     protected Method findMethod(Class<?> clazz, String methodName) { | ||||||
|     @Transactional |  | ||||||
|     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 + "-service-" + this.hostname); |  | ||||||
| 	 |  | ||||||
| 		    	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); |  | ||||||
|     	this.pollMainThreaded(mqsession, mqqueue); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @Threaded(name = "mq-poll", join = true) |  | ||||||
|     private void pollMainThreaded(Session mqsession, Queue mqqueue) throws JMSException { |  | ||||||
| 		MessageConsumer consumer = mqsession.createConsumer(mqqueue); |  | ||||||
| 		try { |  | ||||||
| 			while (!Thread.currentThread().isInterrupted()) { |  | ||||||
| 				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 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 bean enum 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 bean 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 bean 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); |     	Pair<Class<?>, String> key = new Pair<>(clazz, methodName); | ||||||
|     	Method method = this.methodCache.get(key); |     	Method method = this.methodCache.get(key); | ||||||
|     	if (method != null) { |     	if (method != null) { | ||||||
| @@ -439,52 +254,10 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 		this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs())); | 		this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Transactional | 	public abstract void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException; | ||||||
|     public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException { |  | ||||||
|     	this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); |  | ||||||
|     	 |  | ||||||
|     	UUID msgId = UUID.randomUUID(); |  | ||||||
|     	 |  | ||||||
|     	try { |  | ||||||
| 	    	Connection mqcon = this.factory.createConnection(this.username, this.password); |  | ||||||
| 	    	try { |  | ||||||
| 		    	mqcon.setClientID(this.clientId + "-client-" + this.hostname); |  | ||||||
| 		    	 |  | ||||||
| 		    	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); |  | ||||||
| 		    	} finally { |  | ||||||
| 			    	mqsession.close(); |  | ||||||
| 		    	} |  | ||||||
| 	    	} finally { |  | ||||||
| 	    		mqcon.close(); |  | ||||||
| 	    	} |  | ||||||
|     	} catch (JMSException je) { |  | ||||||
|     		throw new AsyncProcessException("A JMS messaging issue occurred", je); |  | ||||||
|     	} |  | ||||||
|     } |  | ||||||
|      |      | ||||||
|     @SuppressWarnings({ "unchecked" }) |     @SuppressWarnings({ "unchecked" }) | ||||||
| 	private Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { | 	protected Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { | ||||||
|     	Class<?> paramType = param.getType(); |     	Class<?> paramType = param.getType(); | ||||||
| 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); | 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); | ||||||
| 
 | 
 | ||||||
| @@ -555,7 +328,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 		} | 		} | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private Object marshal(Object arg) { |     protected Object marshal(Object arg) { | ||||||
|     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { |     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { | ||||||
|     		return arg; |     		return arg; | ||||||
|     	} else if (arg instanceof Temporal) { |     	} else if (arg instanceof Temporal) { | ||||||
| @@ -599,7 +372,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 		} | 		} | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private M2Model loadModel(NodeRef nodeRef) throws IOException { |     protected M2Model loadModel(NodeRef nodeRef) throws IOException { | ||||||
| 		ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); | 		ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); | ||||||
| 		InputStream istream = creader.getContentInputStream(); | 		InputStream istream = creader.getContentInputStream(); | ||||||
| 		try { | 		try { | ||||||
| @@ -22,6 +22,7 @@ import org.quartz.impl.JobDetailImpl; | |||||||
| import org.quartz.impl.StdSchedulerFactory; | import org.quartz.impl.StdSchedulerFactory; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.InitializingBean; | ||||||
| import org.springframework.beans.factory.annotation.Value; | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.context.ApplicationEvent; | import org.springframework.context.ApplicationEvent; | ||||||
| import org.springframework.extensions.surf.util.AbstractLifecycleBean; | import org.springframework.extensions.surf.util.AbstractLifecycleBean; | ||||||
| @@ -32,6 +33,8 @@ import com.inteligr8.alfresco.annotations.job.AsyncJob; | |||||||
| import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncService; | import com.inteligr8.alfresco.annotations.service.AsyncService; | ||||||
| 
 | 
 | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * This class provides a non-persistent alternative to MQ for asynchronous method |  * This class provides a non-persistent alternative to MQ for asynchronous method | ||||||
|  * execution. |  * execution. | ||||||
| @@ -39,7 +42,7 @@ import com.inteligr8.alfresco.annotations.service.AsyncService; | |||||||
|  * @author brian@inteligr8.com |  * @author brian@inteligr8.com | ||||||
|  */ |  */ | ||||||
| @Component("async.thread") | @Component("async.thread") | ||||||
| public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService { | public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean { | ||||||
| 	 | 	 | ||||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
| 	private final JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations"); | 	private final JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations"); | ||||||
| @@ -61,9 +64,19 @@ public class ThreadPoolAsyncService extends AbstractLifecycleBean implements Asy | |||||||
| 	}); | 	}); | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     protected void onBootstrap(ApplicationEvent event) { |     public void afterPropertiesSet() throws Exception { | ||||||
|     	this.queue = new LinkedBlockingQueue<>(this.queueSize); |     	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(); |     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||||
|     	jobDetail.setKey(this.jobKey); |     	jobDetail.setKey(this.jobKey); | ||||||
|     	jobDetail.setRequestsRecovery(true); |     	jobDetail.setRequestsRecovery(true); | ||||||
| @@ -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,50 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import org.springframework.beans.factory.config.ConfigurableBeanFactory; | ||||||
|  | import org.springframework.context.annotation.Scope; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) | ||||||
|  | public class SpringTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public SpringTransactionalAnnotationAdapter(Transactional txl) { | ||||||
|  | 		this.txl = txl; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean isReadOnly() { | ||||||
|  | 		return this.txl.readOnly(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Propagation getPropagation() { | ||||||
|  | 		return this.txl.propagation(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Isolation getIsolation() { | ||||||
|  | 		return this.txl.isolation(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int getTimeoutInSeconds() { | ||||||
|  | 		return this.txl.timeout(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getRollbackFor() { | ||||||
|  | 		return this.txl.rollbackFor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getNoRollbackFor() { | ||||||
|  | 		return this.txl.noRollbackFor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  |  | ||||||
|  | public interface TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	boolean isReadOnly(); | ||||||
|  | 	 | ||||||
|  | 	Propagation getPropagation(); | ||||||
|  | 	 | ||||||
|  | 	Isolation getIsolation(); | ||||||
|  | 	 | ||||||
|  | 	int getTimeoutInSeconds(); | ||||||
|  | 	 | ||||||
|  | 	Class<? extends Throwable>[] getRollbackFor(); | ||||||
|  | 	 | ||||||
|  | 	Class<? extends Throwable>[] getNoRollbackFor(); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -6,6 +6,7 @@ | |||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.ThreadedAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.ThreadedAspect" /> | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" /> | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" /> | ||||||
|  | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.IfAuthorizedAspect" /> | ||||||
| 		 | 		 | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.JobLockAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.JobLockAspect" /> | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect" /> | ||||||
| @@ -13,6 +14,6 @@ | |||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect" /> | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.ChildIsPrimaryAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.ChildIsPrimaryAspect" /> | ||||||
| 
 | 
 | ||||||
| 		<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" /> | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.WebScriptAspect" /> | ||||||
| 	</aspects> | 	</aspects> | ||||||
| </aspectj> | </aspectj> | ||||||
| @@ -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); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								jakarta/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								jakarta/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | <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-jakarta-platform-module</artifactId> | ||||||
|  | 	<packaging>jar</packaging> | ||||||
|  |  | ||||||
|  | 	<properties> | ||||||
|  | 		<alfresco.platform.version>23.2.1</alfresco.platform.version> | ||||||
|  | 		<alfresco.platform.war.version>23.2.0.60</alfresco.platform.war.version> | ||||||
|  | 		<tomcat-rad.version>10-2.1</tomcat-rad.version> | ||||||
|  | 	</properties> | ||||||
|  |  | ||||||
|  | 	<dependencies> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>${project.groupId}</groupId> | ||||||
|  | 			<artifactId>annotations-core-platform-module</artifactId> | ||||||
|  | 			<version>${project.version}</version> | ||||||
|  | 		</dependency> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>jakarta.transaction</groupId> | ||||||
|  | 			<artifactId>jakarta.transaction-api</artifactId> | ||||||
|  | 			<scope>provided</scope> | ||||||
|  | 		</dependency> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>jakarta.servlet</groupId> | ||||||
|  | 			<artifactId>jakarta.servlet-api</artifactId> | ||||||
|  | 			<scope>provided</scope> | ||||||
|  | 		</dependency> | ||||||
|  | 	</dependencies> | ||||||
|  |  | ||||||
|  | 	<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,40 @@ | |||||||
|  | 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 jakarta.transaction.Transactional; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @see jakarta.transaction.Transactional | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") | ||||||
|  | public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public String getJtaInterfaceName() { | ||||||
|  | 		return Transactional.class.getName(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(jakarta.transaction.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isJtaTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | ||||||
|  | 	public void isTransactionalRetryableAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()") | ||||||
|  | 	public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||
|  | 		return super.retryingTransactional(joinPoint); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,96 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.service.impl; | ||||||
|  |  | ||||||
|  | import javax.transaction.xa.XAResource; | ||||||
|  |  | ||||||
|  | import org.alfresco.repo.transaction.AlfrescoTransactionSupport; | ||||||
|  | import org.alfresco.repo.transaction.TransactionListener; | ||||||
|  |  | ||||||
|  | import jakarta.transaction.HeuristicMixedException; | ||||||
|  | import jakarta.transaction.HeuristicRollbackException; | ||||||
|  | import jakarta.transaction.NotSupportedException; | ||||||
|  | import jakarta.transaction.RollbackException; | ||||||
|  | import jakarta.transaction.Status; | ||||||
|  | import jakarta.transaction.Synchronization; | ||||||
|  | import jakarta.transaction.SystemException; | ||||||
|  | import jakarta.transaction.Transaction; | ||||||
|  | import jakarta.transaction.UserTransaction; | ||||||
|  |  | ||||||
|  | public class AlfrescoTransaction implements UserTransaction, Transaction { | ||||||
|  | 	 | ||||||
|  | 	private UserTransaction userTx; | ||||||
|  | 	 | ||||||
|  | 	public AlfrescoTransaction(UserTransaction userTx) { | ||||||
|  | 		this.userTx = userTx; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void begin() throws NotSupportedException, SystemException { | ||||||
|  | 		this.userTx.begin(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, | ||||||
|  | 			SecurityException, IllegalStateException, SystemException { | ||||||
|  | 		this.userTx.commit(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public int getStatus() throws SystemException { | ||||||
|  | 		return this.userTx.getStatus(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void registerSynchronization(Synchronization sync) | ||||||
|  | 			throws RollbackException, IllegalStateException, SystemException { | ||||||
|  | 		AlfrescoTransactionSupport.bindListener(new TransactionListener() { | ||||||
|  | 			@Override | ||||||
|  | 			public void flush() { | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			@Override | ||||||
|  | 			public void beforeCompletion() { | ||||||
|  | 				sync.beforeCompletion(); | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			@Override | ||||||
|  | 			public void beforeCommit(boolean readOnly) { | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			@Override | ||||||
|  | 			public void afterRollback() { | ||||||
|  | 				sync.afterCompletion(Status.STATUS_ROLLEDBACK); | ||||||
|  | 			} | ||||||
|  | 			 | ||||||
|  | 			@Override | ||||||
|  | 			public void afterCommit() { | ||||||
|  | 				sync.afterCompletion(Status.STATUS_COMMITTED); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void rollback() throws IllegalStateException, SecurityException, SystemException { | ||||||
|  | 		this.userTx.rollback(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void setRollbackOnly() throws IllegalStateException, SystemException { | ||||||
|  | 		this.userTx.setRollbackOnly(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void setTransactionTimeout(int seconds) throws SystemException { | ||||||
|  | 		this.userTx.setTransactionTimeout(seconds); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,112 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.service.impl; | ||||||
|  |  | ||||||
|  | import java.util.function.Supplier; | ||||||
|  |  | ||||||
|  | import org.alfresco.service.transaction.TransactionService; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
|  | import jakarta.transaction.HeuristicMixedException; | ||||||
|  | import jakarta.transaction.HeuristicRollbackException; | ||||||
|  | import jakarta.transaction.InvalidTransactionException; | ||||||
|  | import jakarta.transaction.NotSupportedException; | ||||||
|  | import jakarta.transaction.RollbackException; | ||||||
|  | import jakarta.transaction.Status; | ||||||
|  | import jakarta.transaction.SystemException; | ||||||
|  | import jakarta.transaction.Transaction; | ||||||
|  | import jakarta.transaction.TransactionManager; | ||||||
|  | import jakarta.transaction.UserTransaction; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This bean implements a standard TransactionManager for ACS.  | ||||||
|  |  */ | ||||||
|  | @Component | ||||||
|  | public class AlfrescoTransactionManager implements TransactionManager { | ||||||
|  | 	 | ||||||
|  | 	@Autowired | ||||||
|  | 	private TransactionService txService; | ||||||
|  | 	 | ||||||
|  | 	private ThreadLocal<AlfrescoTransaction> tx = ThreadLocal.withInitial(new Supplier<AlfrescoTransaction>() { | ||||||
|  | 		@Override | ||||||
|  | 		public AlfrescoTransaction get() { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void begin() throws NotSupportedException, SystemException { | ||||||
|  | 		UserTransaction userTx = this.txService.getNonPropagatingUserTransaction(); | ||||||
|  | 		AlfrescoTransaction tx = new AlfrescoTransaction(userTx); | ||||||
|  | 		tx.begin(); | ||||||
|  | 		this.tx.set(tx); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, | ||||||
|  | 			SecurityException, IllegalStateException, SystemException { | ||||||
|  | 		AlfrescoTransaction tx = this.tx.get(); | ||||||
|  | 		if (tx == null) | ||||||
|  | 			throw new IllegalStateException(); | ||||||
|  | 		 | ||||||
|  | 		tx.commit(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public int getStatus() throws SystemException { | ||||||
|  | 		AlfrescoTransaction tx = this.tx.get(); | ||||||
|  | 		if (tx == null) | ||||||
|  | 			return Status.STATUS_NO_TRANSACTION; | ||||||
|  | 		 | ||||||
|  | 		return tx.getStatus(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public Transaction getTransaction() throws SystemException { | ||||||
|  | 		return this.tx.get(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void resume(Transaction tx) throws InvalidTransactionException, IllegalStateException, SystemException { | ||||||
|  | 		if (!(tx instanceof AlfrescoTransaction)) | ||||||
|  | 			throw new InvalidTransactionException("An AlfrescoTransaction is expected; received: " + tx.getClass()); | ||||||
|  | 		if (this.tx.get() != null) | ||||||
|  | 			throw new IllegalStateException(); | ||||||
|  | 		 | ||||||
|  | 		this.tx.set((AlfrescoTransaction) tx); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void rollback() throws IllegalStateException, SecurityException, SystemException { | ||||||
|  | 		AlfrescoTransaction tx = this.tx.get(); | ||||||
|  | 		if (tx == null) | ||||||
|  | 			throw new IllegalStateException(); | ||||||
|  | 		 | ||||||
|  | 		tx.rollback(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void setRollbackOnly() throws IllegalStateException, SystemException { | ||||||
|  | 		AlfrescoTransaction tx = this.tx.get(); | ||||||
|  | 		if (tx == null) | ||||||
|  | 			throw new IllegalStateException(); | ||||||
|  | 		 | ||||||
|  | 		tx.setRollbackOnly(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public void setTransactionTimeout(int seconds) throws SystemException { | ||||||
|  | 		AlfrescoTransaction tx = this.tx.get(); | ||||||
|  | 		if (tx != null) | ||||||
|  | 			tx.setTransactionTimeout(seconds); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public Transaction suspend() throws SystemException { | ||||||
|  | 		try { | ||||||
|  | 			return this.tx.get(); | ||||||
|  | 		} finally { | ||||||
|  | 			this.tx.set(null); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,354 @@ | |||||||
|  | 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.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 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.apache.activemq.ActiveMQConnectionFactory; | ||||||
|  | import org.aspectj.lang.ProceedingJoinPoint; | ||||||
|  | import org.aspectj.lang.reflect.MethodSignature; | ||||||
|  | import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; | ||||||
|  | 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 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.Threadable; | ||||||
|  | import com.inteligr8.alfresco.annotations.Threaded; | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | import jakarta.annotation.PreDestroy; | ||||||
|  | import jakarta.jms.Connection; | ||||||
|  | import jakarta.jms.JMSException; | ||||||
|  | import jakarta.jms.Message; | ||||||
|  | import jakarta.jms.MessageConsumer; | ||||||
|  | import jakarta.jms.MessageProducer; | ||||||
|  | import jakarta.jms.Queue; | ||||||
|  | import jakarta.jms.Session; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This class provides integration with MQ for the asynchronous method executions. | ||||||
|  |  *  | ||||||
|  |  * @author brian@inteligr8.com | ||||||
|  |  */ | ||||||
|  | @Component("async.mq") | ||||||
|  | public class MqAsyncService extends AbstractMqAsyncService { | ||||||
|  |  | ||||||
|  | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
|  |  | ||||||
|  |     private JmsPoolConnectionFactory factory; | ||||||
|  |      | ||||||
|  |     /** | ||||||
|  |      * @PostConstruct does not work in ACS | ||||||
|  |      */ | ||||||
|  |     @PostConstruct | ||||||
|  |     protected void init() { | ||||||
|  |     	if (!this.enabled) | ||||||
|  |     		return; | ||||||
|  |  | ||||||
|  | 		super.init(); | ||||||
|  |     	 | ||||||
|  |     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); | ||||||
|  |     	 | ||||||
|  |     	JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory(); | ||||||
|  |     	pool.setConnectionFactory(factory); | ||||||
|  |     	pool.setMaxConnections(this.maxConnections); | ||||||
|  |     	pool.start(); | ||||||
|  |     	 | ||||||
|  |     	this.factory = pool; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @PreDestroy | ||||||
|  |     protected void uninit() { | ||||||
|  | 		super.uninit(); | ||||||
|  |  | ||||||
|  |     	if (this.factory != null) | ||||||
|  |     		this.factory.stop(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     @Transactional | ||||||
|  |     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 + "-service-" + this.hostname); | ||||||
|  | 	 | ||||||
|  | 		    	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); | ||||||
|  |     	this.pollMainThreaded(mqsession, mqqueue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Threaded(name = "mq-poll", join = true) | ||||||
|  |     private void pollMainThreaded(Session mqsession, Queue mqqueue) throws JMSException { | ||||||
|  | 		MessageConsumer consumer = mqsession.createConsumer(mqqueue); | ||||||
|  | 		try { | ||||||
|  | 			while (!Thread.currentThread().isInterrupted()) { | ||||||
|  | 				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 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 bean enum 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 bean 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 bean 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(); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Transactional | ||||||
|  |     public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException { | ||||||
|  |     	this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); | ||||||
|  |     	 | ||||||
|  |     	UUID msgId = UUID.randomUUID(); | ||||||
|  |     	 | ||||||
|  |     	try { | ||||||
|  | 	    	Connection mqcon = this.factory.createConnection(this.username, this.password); | ||||||
|  | 	    	try { | ||||||
|  | 		    	mqcon.setClientID(this.clientId + "-client-" + this.hostname); | ||||||
|  | 		    	 | ||||||
|  | 		    	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); | ||||||
|  | 		    	} finally { | ||||||
|  | 			    	mqsession.close(); | ||||||
|  | 		    	} | ||||||
|  | 	    	} finally { | ||||||
|  | 	    		mqcon.close(); | ||||||
|  | 	    	} | ||||||
|  |     	} catch (JMSException je) { | ||||||
|  |     		throw new AsyncProcessException("A JMS messaging issue occurred", je); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  |  | ||||||
|  | import jakarta.transaction.Transactional; | ||||||
|  |  | ||||||
|  | public class JakartaTransactionalAnnotationAdapter implements JtaTransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	public static final String JTA_INTERFACE_NAME = "jakarta.transaction.Transactional"; | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public static JakartaTransactionalAnnotationAdapter cast(Object obj) { | ||||||
|  | 		return new JakartaTransactionalAnnotationAdapter((Transactional) obj); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public JakartaTransactionalAnnotationAdapter(Transactional txl) { | ||||||
|  | 		this.txl = txl; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean isReadOnly() { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Propagation getPropagation() { | ||||||
|  | 		switch (this.txl.value()) { | ||||||
|  | 			case MANDATORY: | ||||||
|  | 				return Propagation.MANDATORY; | ||||||
|  | 			case REQUIRED: | ||||||
|  | 				return Propagation.REQUIRED; | ||||||
|  | 			case REQUIRES_NEW: | ||||||
|  | 				return Propagation.REQUIRES_NEW; | ||||||
|  | 			case SUPPORTS: | ||||||
|  | 				return Propagation.SUPPORTS; | ||||||
|  | 			case NOT_SUPPORTED: | ||||||
|  | 				return Propagation.NOT_SUPPORTED; | ||||||
|  | 			case NEVER: | ||||||
|  | 				return Propagation.NEVER; | ||||||
|  | 			default: | ||||||
|  | 				throw new IllegalStateException("This should never happen"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Isolation getIsolation() { | ||||||
|  | 		return Isolation.DEFAULT; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int getTimeoutInSeconds() { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@SuppressWarnings("unchecked") | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getRollbackFor() { | ||||||
|  | 		return this.txl.rollbackOn(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@SuppressWarnings("unchecked") | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getNoRollbackFor() { | ||||||
|  | 		return this.txl.dontRollbackOn(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								jakarta/src/main/resources/META-INF/aop.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								jakarta/src/main/resources/META-INF/aop.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <aspectj> | ||||||
|  | 	<aspects> | ||||||
|  | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" /> | ||||||
|  | 	</aspects> | ||||||
|  | </aspectj> | ||||||
| @@ -1,7 +1,8 @@ | |||||||
| module.id=${project.groupId}.${project.artifactId} | module.id=${project.groupId}.${project.artifactId} | ||||||
|  | module.alias=${project.groupId}.annotations-platform-module | ||||||
| module.title=${project.name} | module.title=${project.name} | ||||||
| module.description=${project.description} | module.description=${project.description} | ||||||
| module.version=${project.version} | module.version=${module.version} | ||||||
| 
 | 
 | ||||||
| module.repo.version.min=6.0 | module.repo.version.min=6.0 | ||||||
| #module.repo.version.max= | #module.repo.version.max= | ||||||
| @@ -44,9 +44,8 @@ public class TransactionalTest extends AbstractLifecycleBean { | |||||||
| 		 | 		 | ||||||
| 		try { | 		try { | ||||||
| 			this.tryNoSupportsTransactional(); | 			this.tryNoSupportsTransactional(); | ||||||
| 			throw new IllegalStateException(); |  | ||||||
| 		} catch (IllegalTransactionStateException uoe) { | 		} catch (IllegalTransactionStateException uoe) { | ||||||
| 			// suppress | 			throw new IllegalStateException(); | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		try { | 		try { | ||||||
| @@ -200,7 +199,8 @@ public class TransactionalTest extends AbstractLifecycleBean { | |||||||
| 	 | 	 | ||||||
| 	@Transactional(propagation = Propagation.NOT_SUPPORTED) | 	@Transactional(propagation = Propagation.NOT_SUPPORTED) | ||||||
| 	private void tryNoSupportsTransactional() { | 	private void tryNoSupportsTransactional() { | ||||||
| 		throw new UnsupportedOperationException(); | 		Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction"); | ||||||
|  | 		Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction"); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@Transactional(propagation = Propagation.NEVER) | 	@Transactional(propagation = Propagation.NEVER) | ||||||
							
								
								
									
										
											BIN
										
									
								
								javax/metadata.keystore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								javax/metadata.keystore
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										54
									
								
								javax/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								javax/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | <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-javax-platform-module</artifactId> | ||||||
|  | 	<packaging>jar</packaging> | ||||||
|  | 	 | ||||||
|  | 	<properties> | ||||||
|  | 		<alfresco.platform.war.version>22.22</alfresco.platform.war.version> | ||||||
|  | 	</properties> | ||||||
|  |  | ||||||
|  | 	<dependencies> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>${project.groupId}</groupId> | ||||||
|  | 			<artifactId>annotations-core-platform-module</artifactId> | ||||||
|  | 			<version>${project.version}</version> | ||||||
|  | 		</dependency> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>javax.transaction</groupId> | ||||||
|  | 			<artifactId>javax.transaction-api</artifactId> | ||||||
|  | 			<version>1.3</version> | ||||||
|  | 			<scope>provided</scope> | ||||||
|  | 		</dependency> | ||||||
|  | 	</dependencies> | ||||||
|  |  | ||||||
|  | 	<build> | ||||||
|  | 		<plugins> | ||||||
|  | 			<plugin> | ||||||
|  | 				<groupId>io.repaint.maven</groupId> | ||||||
|  | 				<artifactId>tiles-maven-plugin</artifactId> | ||||||
|  | 				<version>2.40</version> | ||||||
|  | 				<extensions>true</extensions> | ||||||
|  | 				<configuration> | ||||||
|  | 					<tiles> | ||||||
|  | 						<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile --> | ||||||
|  | 						<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0)</tile> | ||||||
|  | 						<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile --> | ||||||
|  | 						<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0)</tile> | ||||||
|  | 						<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile --> | ||||||
|  | 						<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0)</tile> | ||||||
|  | 					</tiles> | ||||||
|  | 				</configuration> | ||||||
|  | 			</plugin> | ||||||
|  | 		</plugins> | ||||||
|  | 	</build> | ||||||
|  | </project> | ||||||
							
								
								
									
										74
									
								
								javax/rad.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								javax/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
									
								
								javax/rad.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								javax/rad.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | #!/bin/sh | ||||||
|  |  | ||||||
|  | discoverArtifactId() { | ||||||
|  | 	ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | rebuild() { | ||||||
|  | 	echo "Rebuilding project ..." | ||||||
|  | 	mvn process-test-classes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | start() { | ||||||
|  | 	echo "Rebuilding project and starting Docker containers to support rapid application development ..." | ||||||
|  | 	mvn -Drad process-test-classes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | start_log() { | ||||||
|  | 	echo "Rebuilding project and starting Docker containers to support rapid application development ..." | ||||||
|  | 	mvn -Drad -Ddocker.showLogs process-test-classes | ||||||
|  | } | ||||||
|  |  | ||||||
|  | stop() { | ||||||
|  | 	discoverArtifactId | ||||||
|  | 	echo "Stopping Docker containers that supported rapid application development ..." | ||||||
|  | 	docker container ls --filter name=${ARTIFACT_ID}-* | ||||||
|  | 	echo "Stopping containers ..." | ||||||
|  | 	docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*` | ||||||
|  | 	echo "Removing containers ..." | ||||||
|  | 	docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | tail_logs() { | ||||||
|  | 	discoverArtifactId | ||||||
|  | 	docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | list() { | ||||||
|  | 	discoverArtifactId | ||||||
|  | 	docker container ls --filter name=${ARTIFACT_ID}-* | ||||||
|  | } | ||||||
|  |  | ||||||
|  | case "$1" in | ||||||
|  | 	start) | ||||||
|  | 		start | ||||||
|  | 		;; | ||||||
|  | 	start_log) | ||||||
|  | 		start_log | ||||||
|  | 		;; | ||||||
|  | 	stop) | ||||||
|  | 		stop | ||||||
|  | 		;; | ||||||
|  | 	restart) | ||||||
|  | 		stop | ||||||
|  | 		start | ||||||
|  | 		;; | ||||||
|  | 	rebuild) | ||||||
|  | 		rebuild | ||||||
|  | 		;; | ||||||
|  | 	tail) | ||||||
|  | 		tail_logs $2 | ||||||
|  | 		;; | ||||||
|  | 	containers) | ||||||
|  | 		list | ||||||
|  | 		;; | ||||||
|  | 	*) | ||||||
|  | 		echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" | ||||||
|  | 		exit 1 | ||||||
|  | esac | ||||||
|  |  | ||||||
|  | echo "Completed!" | ||||||
|  |  | ||||||
| @@ -0,0 +1,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.DeclarePrecedence; | ||||||
|  | import org.aspectj.lang.annotation.Pointcut; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @see javax.transaction.Transactional | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") | ||||||
|  | public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public String getJtaInterfaceName() { | ||||||
|  | 		return javax.transaction.Transactional.class.getName(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isJtaTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | ||||||
|  | 	public void isTransactionalRetryableAnnotated() { | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()") | ||||||
|  | 	public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||
|  | 		return super.retryingTransactional(joinPoint); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,296 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.service.impl; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.lang.reflect.InvocationTargetException; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.Parameter; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.UUID; | ||||||
|  | import java.util.regex.Matcher; | ||||||
|  | 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 org.alfresco.service.cmr.repository.NodeRef; | ||||||
|  | import org.apache.activemq.ActiveMQConnectionFactory; | ||||||
|  | import org.apache.activemq.jms.pool.PooledConnectionFactory; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
|  |  | ||||||
|  | import com.inteligr8.alfresco.annotations.AuthorizedAsSystem; | ||||||
|  | import com.inteligr8.alfresco.annotations.Threaded; | ||||||
|  | import com.inteligr8.alfresco.annotations.TransactionalRetryable; | ||||||
|  | import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | import jakarta.annotation.PreDestroy; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This class provides integration with MQ for the asynchronous method executions. | ||||||
|  |  *  | ||||||
|  |  * @author brian@inteligr8.com | ||||||
|  |  */ | ||||||
|  | @Component("async.mq") | ||||||
|  | public class MqAsyncService extends AbstractMqAsyncService { | ||||||
|  |  | ||||||
|  | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
|  |  | ||||||
|  |     private PooledConnectionFactory factory; | ||||||
|  |      | ||||||
|  |     /** | ||||||
|  |      * @PostConstruct does not work in ACS | ||||||
|  |      */ | ||||||
|  |     @PostConstruct | ||||||
|  |     protected void init() { | ||||||
|  |     	if (!this.enabled) | ||||||
|  |     		return; | ||||||
|  |  | ||||||
|  | 		super.init(); | ||||||
|  |     	 | ||||||
|  |     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); | ||||||
|  |     	 | ||||||
|  |     	PooledConnectionFactory pool = new PooledConnectionFactory(); | ||||||
|  |     	pool.setConnectionFactory(factory); | ||||||
|  |     	pool.setMaxConnections(this.maxConnections); | ||||||
|  |     	pool.start(); | ||||||
|  |     	 | ||||||
|  |     	this.factory = pool; | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @PreDestroy | ||||||
|  |     protected void uninit() { | ||||||
|  | 		super.uninit(); | ||||||
|  |  | ||||||
|  |     	if (this.factory != null) | ||||||
|  |     		this.factory.stop(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     @Transactional | ||||||
|  |     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 + "-service-" + this.hostname); | ||||||
|  | 	 | ||||||
|  | 		    	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); | ||||||
|  |     	this.pollMainThreaded(mqsession, mqqueue); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Threaded(name = "mq-poll", join = true) | ||||||
|  |     private void pollMainThreaded(Session mqsession, Queue mqqueue) throws JMSException { | ||||||
|  | 		MessageConsumer consumer = mqsession.createConsumer(mqqueue); | ||||||
|  | 		try { | ||||||
|  | 			while (!Thread.currentThread().isInterrupted()) { | ||||||
|  | 				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 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 bean enum 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 bean 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 bean 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(); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Transactional | ||||||
|  |     public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException { | ||||||
|  |     	this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); | ||||||
|  |     	 | ||||||
|  |     	UUID msgId = UUID.randomUUID(); | ||||||
|  |     	 | ||||||
|  |     	try { | ||||||
|  | 	    	Connection mqcon = this.factory.createConnection(this.username, this.password); | ||||||
|  | 	    	try { | ||||||
|  | 		    	mqcon.setClientID(this.clientId + "-client-" + this.hostname); | ||||||
|  | 		    	 | ||||||
|  | 		    	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); | ||||||
|  | 		    	} finally { | ||||||
|  | 			    	mqsession.close(); | ||||||
|  | 		    	} | ||||||
|  | 	    	} finally { | ||||||
|  | 	    		mqcon.close(); | ||||||
|  | 	    	} | ||||||
|  |     	} catch (JMSException je) { | ||||||
|  |     		throw new AsyncProcessException("A JMS messaging issue occurred", je); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import javax.transaction.Transactional; | ||||||
|  |  | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  |  | ||||||
|  | public class JavaxTransactionalAnnotationAdapter implements JtaTransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	public static final String JTA_INTERFACE_NAME = "javax.transaction.Transactional"; | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public static JavaxTransactionalAnnotationAdapter cast(Object obj) { | ||||||
|  | 		return new JavaxTransactionalAnnotationAdapter((Transactional) obj); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public JavaxTransactionalAnnotationAdapter(Transactional txl) { | ||||||
|  | 		this.txl = txl; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean isReadOnly() { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Propagation getPropagation() { | ||||||
|  | 		switch (this.txl.value()) { | ||||||
|  | 			case MANDATORY: | ||||||
|  | 				return Propagation.MANDATORY; | ||||||
|  | 			case REQUIRED: | ||||||
|  | 				return Propagation.REQUIRED; | ||||||
|  | 			case REQUIRES_NEW: | ||||||
|  | 				return Propagation.REQUIRES_NEW; | ||||||
|  | 			case SUPPORTS: | ||||||
|  | 				return Propagation.SUPPORTS; | ||||||
|  | 			case NOT_SUPPORTED: | ||||||
|  | 				return Propagation.NOT_SUPPORTED; | ||||||
|  | 			case NEVER: | ||||||
|  | 				return Propagation.NEVER; | ||||||
|  | 			default: | ||||||
|  | 				throw new IllegalStateException("This should never happen"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Isolation getIsolation() { | ||||||
|  | 		return Isolation.DEFAULT; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int getTimeoutInSeconds() { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@SuppressWarnings("unchecked") | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getRollbackFor() { | ||||||
|  | 		return this.txl.rollbackOn(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@SuppressWarnings("unchecked") | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getNoRollbackFor() { | ||||||
|  | 		return this.txl.dontRollbackOn(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								javax/src/main/resources/META-INF/aop.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								javax/src/main/resources/META-INF/aop.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <aspectj> | ||||||
|  | 	<aspects> | ||||||
|  | 		<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" /> | ||||||
|  | 	</aspects> | ||||||
|  | </aspectj> | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | module.id=${project.groupId}.${project.artifactId} | ||||||
|  | module.alias=${project.groupId}.annotations-platform-module | ||||||
|  | module.title=${project.name} | ||||||
|  | module.description=${project.description} | ||||||
|  | module.version=${module.version} | ||||||
|  |  | ||||||
|  | module.repo.version.min=6.0 | ||||||
|  | #module.repo.version.max= | ||||||
|  |  | ||||||
|  | module.depends.com.inteligr8.alfresco.aspectj-platform-module=1.0-* | ||||||
| @@ -0,0 +1,224 @@ | |||||||
|  | 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.stereotype.Component; | ||||||
|  | import org.springframework.transaction.IllegalTransactionStateException; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
|  | import org.springframework.util.Assert; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | public class TransactionalTest 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); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										37
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -5,8 +5,8 @@ | |||||||
| 	 | 	 | ||||||
| 	<groupId>com.inteligr8.alfresco</groupId> | 	<groupId>com.inteligr8.alfresco</groupId> | ||||||
| 	<artifactId>annotations-platform-module</artifactId> | 	<artifactId>annotations-platform-module</artifactId> | ||||||
| 	<version>1.0.3</version> | 	<version>1.0-SNAPSHOT</version> | ||||||
| 	<packaging>jar</packaging> | 	<packaging>pom</packaging> | ||||||
| 	 | 	 | ||||||
| 	<name>Annotations ACS Platform Module</name> | 	<name>Annotations ACS Platform Module</name> | ||||||
| 	<description>A module to support annotation-based development for Alfresco Content Services modules.</description> | 	<description>A module to support annotation-based development for Alfresco Content Services modules.</description> | ||||||
| @@ -43,7 +43,7 @@ | |||||||
| 		<maven.compiler.target>8</maven.compiler.target> | 		<maven.compiler.target>8</maven.compiler.target> | ||||||
|  |  | ||||||
| 		<alfresco.sdk.version>4.8.0</alfresco.sdk.version> | 		<alfresco.sdk.version>4.8.0</alfresco.sdk.version> | ||||||
| 		<alfresco.platform.version>7.4.2</alfresco.platform.version> | 		<alfresco.platform.version>7.4.1</alfresco.platform.version> | ||||||
| 		<alfresco.platform.war.version>22.22</alfresco.platform.war.version> | 		<alfresco.platform.war.version>22.22</alfresco.platform.war.version> | ||||||
| 		<aspectj.version>1.9.19</aspectj.version> | 		<aspectj.version>1.9.19</aspectj.version> | ||||||
| 		<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts> | 		<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts> | ||||||
| @@ -85,28 +85,19 @@ | |||||||
| 		</dependency> | 		</dependency> | ||||||
| 	</dependencies> | 	</dependencies> | ||||||
| 	 | 	 | ||||||
| 	<build> | 	<modules> | ||||||
| 		<plugins> | 		<module>core</module> | ||||||
| 			<plugin> | 		<module>javax</module> | ||||||
| 				<groupId>io.repaint.maven</groupId> | 		<module>jakarta</module> | ||||||
| 				<artifactId>tiles-maven-plugin</artifactId> | 	</modules> | ||||||
| 				<version>2.33</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> |  | ||||||
|  |  | ||||||
| 	<profiles> | 	<profiles> | ||||||
|  | 		<profile> | ||||||
|  | 			<id>debug</id> | ||||||
|  | 			<properties> | ||||||
|  | 				<docker.showLogs>true</docker.showLogs> | ||||||
|  | 			</properties> | ||||||
|  | 		</profile> | ||||||
| 		<profile> | 		<profile> | ||||||
| 			<id>ossrh-release</id> | 			<id>ossrh-release</id> | ||||||
| 			<properties> | 			<properties> | ||||||
|   | |||||||
| @@ -1,113 +0,0 @@ | |||||||
| package com.inteligr8.alfresco.annotations; |  | ||||||
|  |  | ||||||
| import java.lang.reflect.Method; |  | ||||||
|  |  | ||||||
| import org.springframework.transaction.annotation.Isolation; |  | ||||||
| import org.springframework.transaction.annotation.Propagation; |  | ||||||
| import org.springframework.transaction.annotation.Transactional; |  | ||||||
|  |  | ||||||
| public class TransactionalWrapper { |  | ||||||
| 	 |  | ||||||
| 	private Transactional stxl = null; |  | ||||||
| 	private javax.transaction.Transactional jtxl = null; |  | ||||||
| 	 |  | ||||||
| 	public TransactionalWrapper(Transactional txl) { |  | ||||||
| 		this.stxl = txl; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public TransactionalWrapper(javax.transaction.Transactional txl) { |  | ||||||
| 		this.jtxl = txl; |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public static TransactionalWrapper wrap(Method method) { |  | ||||||
| 		Transactional stxl = method.getAnnotation(Transactional.class); |  | ||||||
| 		javax.transaction.Transactional jtxl = method.getAnnotation(javax.transaction.Transactional.class); |  | ||||||
| 		if (stxl == null && jtxl == null) { |  | ||||||
| 			return null; |  | ||||||
| 		} else if (stxl != null) { |  | ||||||
| 			return new TransactionalWrapper(stxl); |  | ||||||
| 		} else if (jtxl != null) { |  | ||||||
| 			return new TransactionalWrapper(jtxl); |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public boolean isReadOnly() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.readOnly(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			return false; |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public Propagation getPropagation() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.propagation(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			switch (this.jtxl.value()) { |  | ||||||
| 				case MANDATORY: |  | ||||||
| 					return Propagation.MANDATORY; |  | ||||||
| 				case REQUIRED: |  | ||||||
| 					return Propagation.REQUIRED; |  | ||||||
| 				case REQUIRES_NEW: |  | ||||||
| 					return Propagation.REQUIRES_NEW; |  | ||||||
| 				case SUPPORTS: |  | ||||||
| 					return Propagation.SUPPORTS; |  | ||||||
| 				case NOT_SUPPORTED: |  | ||||||
| 					return Propagation.NOT_SUPPORTED; |  | ||||||
| 				case NEVER: |  | ||||||
| 					return Propagation.NEVER; |  | ||||||
| 				default: |  | ||||||
| 					throw new IllegalStateException("This should never happen"); |  | ||||||
| 			} |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public Isolation getIsolation() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.isolation(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			return Isolation.DEFAULT; |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	public int getTimeoutInSeconds() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.timeout(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			return 0; |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	@SuppressWarnings("unchecked") |  | ||||||
| 	public Class<? extends Throwable>[] getRollbackFor() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.rollbackFor(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			return this.jtxl.rollbackOn(); |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	@SuppressWarnings("unchecked") |  | ||||||
| 	public Class<? extends Throwable>[] getNoRollbackFor() { |  | ||||||
| 		if (this.stxl != null) { |  | ||||||
| 			return this.stxl.noRollbackFor(); |  | ||||||
| 		} else if (this.jtxl != null) { |  | ||||||
| 			return this.jtxl.dontRollbackOn(); |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalStateException("This should never happen"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user