Compare commits
	
		
			16 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4162c762f2 | |||
| d679d7efcc | |||
| cfe415ef27 | |||
| 0d5a89c456 | |||
| 38b9f6e35f | |||
| 2158c6d8c7 | |||
| 7bc7e0cc34 | |||
| 62da2b3830 | |||
| 00036df1a9 | |||
| cfcb7fd75a | |||
| 240adf865d | |||
| ba7609ec06 | |||
| ed3e01e9a5 | |||
| bca4c40131 | |||
| 83397e3578 | |||
| a9cb101ff6 | 
							
								
								
									
										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.1.0</version> | ||||||
|  | 		<relativePath>../</relativePath> | ||||||
|  | 	</parent> | ||||||
|  | 	 | ||||||
|  | 	<artifactId>annotations-core-platform-module</artifactId> | ||||||
|  | 	<packaging>jar</packaging> | ||||||
|  | </project> | ||||||
| @@ -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; | ||||||
| @@ -9,17 +10,20 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti | |||||||
| 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.Around; | ||||||
| import org.aspectj.lang.annotation.Aspect; |  | ||||||
| import org.aspectj.lang.annotation.DeclarePrecedence; | import org.aspectj.lang.annotation.DeclarePrecedence; | ||||||
| import org.aspectj.lang.annotation.Pointcut; | 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,22 +41,24 @@ 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 |  | ||||||
| @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") | @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") | ||||||
| public class RetryingTransactionAspect { | public abstract class AbstractRetryingTransactionAspect { | ||||||
| 	 | 	 | ||||||
| 	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; | ||||||
| 	 | 	 | ||||||
|  | 	public abstract String getJtaInterfaceName(); | ||||||
|  | 	 | ||||||
| 	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | 	@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") | ||||||
| 	public void isTransactionalAnnotated() { | 	public void isTransactionalAnnotated() { | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | 	public abstract void isJtaTransactionalAnnotated(); | ||||||
| 	public void isJtaTransactionalAnnotated() { |  | ||||||
| 	} |  | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | 	@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") | ||||||
| 	public void isTransactionalRetryableAnnotated() { | 	public void isTransactionalRetryableAnnotated() { | ||||||
| @@ -63,7 +69,7 @@ public class RetryingTransactionAspect { | |||||||
| 		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 +83,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 +114,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 +143,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,8 +165,15 @@ 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: | ||||||
|  | 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | ||||||
|  | 					case TXN_NONE: | ||||||
|  | 						return false; | ||||||
|  | 					default: | ||||||
|  | 						throw new IllegalTransactionStateException("A transaction exists and pausing it is not supported"); | ||||||
|  | 				} | ||||||
| 			case SUPPORTS: | 			case SUPPORTS: | ||||||
| 			//case NOT_SUPPORTED:  not supported; we would have to create another thread to simulate |  | ||||||
| 				return false; | 				return false; | ||||||
| 			case REQUIRED: | 			case REQUIRED: | ||||||
| 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | ||||||
| @@ -153,7 +189,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 { | ||||||
| @@ -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,385 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.service.impl; | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.lang.reflect.Constructor; | ||||||
|  | import java.lang.reflect.InvocationTargetException; | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | import java.lang.reflect.Parameter; | ||||||
|  | import java.net.InetAddress; | ||||||
|  | import java.net.UnknownHostException; | ||||||
|  | import java.time.Instant; | ||||||
|  | import java.time.LocalDate; | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  | import java.time.LocalTime; | ||||||
|  | import java.time.OffsetDateTime; | ||||||
|  | import java.time.OffsetTime; | ||||||
|  | import java.time.ZonedDateTime; | ||||||
|  | import java.time.format.DateTimeFormatter; | ||||||
|  | import java.time.temporal.Temporal; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Map.Entry; | ||||||
|  | import java.util.function.Supplier; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
|  | import org.alfresco.error.AlfrescoRuntimeException; | ||||||
|  | import org.alfresco.model.ContentModel; | ||||||
|  | import org.alfresco.repo.cache.SimpleCache; | ||||||
|  | import org.alfresco.repo.dictionary.M2Model; | ||||||
|  | import org.alfresco.repo.version.common.VersionImpl; | ||||||
|  | import org.alfresco.service.cmr.action.Action; | ||||||
|  | import org.alfresco.service.cmr.action.ActionService; | ||||||
|  | import org.alfresco.service.cmr.dictionary.CustomModelService; | ||||||
|  | import org.alfresco.service.cmr.dictionary.DictionaryService; | ||||||
|  | import org.alfresco.service.cmr.repository.ContentReader; | ||||||
|  | import org.alfresco.service.cmr.repository.ContentService; | ||||||
|  | import org.alfresco.service.cmr.repository.NodeRef; | ||||||
|  | import org.alfresco.service.cmr.version.Version; | ||||||
|  | import org.alfresco.service.namespace.NamespaceService; | ||||||
|  | import org.alfresco.service.namespace.QName; | ||||||
|  | import org.alfresco.service.transaction.TransactionService; | ||||||
|  | import org.alfresco.util.Pair; | ||||||
|  | import org.aspectj.lang.ProceedingJoinPoint; | ||||||
|  | import org.aspectj.lang.reflect.MethodSignature; | ||||||
|  | import org.quartz.JobKey; | ||||||
|  | import org.quartz.Scheduler; | ||||||
|  | import org.quartz.SchedulerException; | ||||||
|  | import org.quartz.Trigger; | ||||||
|  | import org.quartz.TriggerBuilder; | ||||||
|  | import org.quartz.impl.JobDetailImpl; | ||||||
|  | import org.quartz.impl.StdSchedulerFactory; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  | import org.springframework.beans.factory.DisposableBean; | ||||||
|  | import org.springframework.beans.factory.InitializingBean; | ||||||
|  | import org.springframework.beans.factory.annotation.Autowired; | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.context.ApplicationEvent; | ||||||
|  | import org.springframework.extensions.surf.util.AbstractLifecycleBean; | ||||||
|  | import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
|  | import com.inteligr8.alfresco.annotations.Threadable; | ||||||
|  | import com.inteligr8.alfresco.annotations.job.AsyncJob; | ||||||
|  | import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||||
|  | import com.inteligr8.alfresco.annotations.service.AsyncService; | ||||||
|  |  | ||||||
|  | import jakarta.annotation.PostConstruct; | ||||||
|  | import jakarta.annotation.PreDestroy; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author brian@inteligr8.com | ||||||
|  |  */ | ||||||
|  | public abstract class AbstractMqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable { | ||||||
|  | 	 | ||||||
|  | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
|  | 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | ||||||
|  | 	protected final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); | ||||||
|  | 	protected final ObjectMapper om = new ObjectMapper(); | ||||||
|  |      | ||||||
|  | 	@Value("${inteligr8.async.mq.enabled}") | ||||||
|  | 	protected boolean enabled; | ||||||
|  | 	 | ||||||
|  | 	@Value("${inteligr8.async.mq.workerThreads}") | ||||||
|  | 	protected int workerThreads; | ||||||
|  | 	 | ||||||
|  |     @Value("${inteligr8.async.mq.url}") | ||||||
|  |     protected String url; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.username}") | ||||||
|  |     protected String username; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.password}") | ||||||
|  |     protected String password; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.queue}") | ||||||
|  |     protected String queueName; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.errorQueue}") | ||||||
|  |     protected String errorQueueName; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.clientId}") | ||||||
|  |     protected String clientId; | ||||||
|  |      | ||||||
|  |     @Value("${inteligr8.async.mq.pool.max}") | ||||||
|  |     protected short maxConnections; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected ActionService actionService; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected ContentService contentService; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected CustomModelService modelService; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected DictionaryService dictionaryService; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected NamespaceService namespaceService; | ||||||
|  |      | ||||||
|  |     @Autowired | ||||||
|  |     protected TransactionService txService; | ||||||
|  |      | ||||||
|  |     protected String hostname; | ||||||
|  |      | ||||||
|  |     protected SimpleCache<Pair<Class<?>, String>, Method> methodCache; | ||||||
|  |      | ||||||
|  |     protected ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() { | ||||||
|  |     	@Override | ||||||
|  |     	public Boolean get() { | ||||||
|  |     		return false; | ||||||
|  |     	} | ||||||
|  | 	}); | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     public final void afterPropertiesSet() throws Exception { | ||||||
|  |     	this.init(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     public final void destroy() throws Exception { | ||||||
|  |     	this.uninit(); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     /** | ||||||
|  |      * @PostConstruct does not work in ACS | ||||||
|  |      */ | ||||||
|  |     @PostConstruct | ||||||
|  |     protected void init() { | ||||||
|  |     	if (!this.enabled) | ||||||
|  |     		return; | ||||||
|  |     	 | ||||||
|  |     	try { | ||||||
|  |     		this.hostname = InetAddress.getLocalHost().getHostName(); | ||||||
|  |     	} catch (UnknownHostException uhe) { | ||||||
|  |     		this.hostname = "unknown"; | ||||||
|  |     	} | ||||||
|  |  | ||||||
|  | 		if (this.workerThreads <= 0) | ||||||
|  | 			throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive"); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @PreDestroy | ||||||
|  |     protected void uninit() { | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     protected void onBootstrap(ApplicationEvent event) { | ||||||
|  |     	if (!this.enabled) | ||||||
|  |     		return; | ||||||
|  |     	 | ||||||
|  |     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||||
|  |     	jobDetail.setKey(this.jobKey); | ||||||
|  |     	jobDetail.setRequestsRecovery(true); | ||||||
|  |     	jobDetail.setJobClass(AsyncJob.class); | ||||||
|  |     	jobDetail.getJobDataMap().put("asyncService", this); | ||||||
|  |     	 | ||||||
|  |     	Trigger trigger = TriggerBuilder.newTrigger() | ||||||
|  |     			.startNow() | ||||||
|  |     			.build(); | ||||||
|  |     	 | ||||||
|  |     	try { | ||||||
|  | 	    	StdSchedulerFactory.getDefaultScheduler() | ||||||
|  | 					.scheduleJob(jobDetail, trigger); | ||||||
|  |     	} catch (SchedulerException se) { | ||||||
|  |     		this.logger.error("The MQ async service job failed to start; no asynchronous executions will be processed!", se); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     protected void onShutdown(ApplicationEvent event) { | ||||||
|  |     	try { | ||||||
|  | 	    	Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); | ||||||
|  | 	    	scheduler.deleteJob(this.jobKey); | ||||||
|  |     	} catch (SchedulerException se) { | ||||||
|  |     		this.logger.warn("The MQ async service job failed to stop", se); | ||||||
|  |     	} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  | 		return this.enabled; | ||||||
|  | 	} | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  | 	public Integer getThreads() { | ||||||
|  | 		return this.workerThreads; | ||||||
|  | 	} | ||||||
|  |      | ||||||
|  |     @Override | ||||||
|  | 	public boolean isCurrentThreadAsynchronous() { | ||||||
|  | 		return this.isAsync.get(); | ||||||
|  | 	} | ||||||
|  |      | ||||||
|  |     protected Method findMethod(Class<?> clazz, String methodName) { | ||||||
|  |     	Pair<Class<?>, String> key = new Pair<>(clazz, methodName); | ||||||
|  |     	Method method = this.methodCache.get(key); | ||||||
|  |     	if (method != null) { | ||||||
|  | 			this.logger.trace("Found method in cache: {}", method); | ||||||
|  |     		return method; | ||||||
|  |     	} | ||||||
|  |  | ||||||
|  | 		this.logger.trace("Looping through bean type methods to find: {}", methodName); | ||||||
|  | 		 | ||||||
|  | 		for (Method amethod : clazz.getDeclaredMethods()) { | ||||||
|  | 			if (amethod.getName().equals(methodName)) { | ||||||
|  | 				this.logger.debug("Found and caching method: {} => {}", key, amethod); | ||||||
|  | 				this.methodCache.put(key, amethod); | ||||||
|  | 				return amethod; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		throw new IllegalStateException("The bean (" + clazz + ") does not implement the method: " + methodName); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException { | ||||||
|  |     	this.logger.trace("push({})", joinPoint); | ||||||
|  |     	 | ||||||
|  | 		if (!(joinPoint.getSignature() instanceof MethodSignature)) | ||||||
|  | 			throw new IllegalStateException("The join point must be on methods and methods have signatures"); | ||||||
|  | 		 | ||||||
|  | 		Object bean = joinPoint.getThis(); | ||||||
|  | 		this.logger.debug("Queuing for bean: {}", bean.getClass()); | ||||||
|  |  | ||||||
|  | 		MethodSignature methodSig = (MethodSignature) joinPoint.getSignature(); | ||||||
|  | 		Method method = methodSig.getMethod(); | ||||||
|  | 		this.logger.debug("Queuing for method: {}", method); | ||||||
|  | 		 | ||||||
|  | 		this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 	public abstract void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException; | ||||||
|  |      | ||||||
|  |     @SuppressWarnings({ "unchecked" }) | ||||||
|  | 	protected Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { | ||||||
|  |     	Class<?> paramType = param.getType(); | ||||||
|  | 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); | ||||||
|  |  | ||||||
|  |     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { | ||||||
|  | 			this.logger.trace("Unmarshaling primitive: {}", arg); | ||||||
|  | 			return arg; | ||||||
|  |     	} else if (Temporal.class.isAssignableFrom(paramType)) { | ||||||
|  |     		if (OffsetDateTime.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return OffsetDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(arg.toString())); | ||||||
|  |     		} else if (ZonedDateTime.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(arg.toString())); | ||||||
|  |     		} else if (LocalDate.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(arg.toString())); | ||||||
|  |     		} else if (LocalDateTime.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return LocalDateTime.from(DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(arg.toString())); | ||||||
|  |     		} else if (Instant.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return Instant.from(DateTimeFormatter.ISO_INSTANT.parse(arg.toString())); | ||||||
|  |     		} else if (LocalTime.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return LocalTime.from(DateTimeFormatter.ISO_LOCAL_TIME.parse(arg.toString())); | ||||||
|  |     		} else if (OffsetTime.class.isAssignableFrom(paramType)) { | ||||||
|  |     			return OffsetTime.from(DateTimeFormatter.ISO_OFFSET_TIME.parse(arg.toString())); | ||||||
|  |     		} else { | ||||||
|  |     			throw new UnsupportedOperationException(); | ||||||
|  |     		} | ||||||
|  |     	} else if (Version.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as JSON object: {}", arg); | ||||||
|  | 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); | ||||||
|  | 			 | ||||||
|  | 			Map<String, Serializable> versionPropertiesMap = (Map<String, Serializable>) argMap.get("properties"); | ||||||
|  | 			NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef")); | ||||||
|  | 			 | ||||||
|  | 			Version version = new VersionImpl(versionPropertiesMap, nodeRef); | ||||||
|  | 			this.logger.trace("Unmarshaled version: {} = {}", param.getName(), version); | ||||||
|  | 			return version; | ||||||
|  | 		} else if (Action.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as JSON object: {}", arg); | ||||||
|  | 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); | ||||||
|  | 			 | ||||||
|  | 			String actionId = (String) argMap.get("actionId"); | ||||||
|  | 			NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef")); | ||||||
|  | 			this.logger.trace("Unmarshaling action: {}, {}", actionId, nodeRef); | ||||||
|  | 			 | ||||||
|  | 			Action action = this.actionService.getAction(nodeRef, actionId); | ||||||
|  | 			this.logger.trace("Unmarshaled action: {} = {}", param.getName(), action); | ||||||
|  | 			return action; | ||||||
|  | 		} else if (Collection.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as JSON array: {}", arg); | ||||||
|  | 			return this.om.convertValue(arg, Collection.class); | ||||||
|  | 		} else if (Map.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as JSON object: {}", arg); | ||||||
|  | 			return this.om.convertValue(arg, Map.class); | ||||||
|  | 		} else if (QName.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as QName: {}", arg); | ||||||
|  | 			return QName.createQName((String) arg); | ||||||
|  | 		} else if (Enum.class.isAssignableFrom(paramType)) { | ||||||
|  | 			this.logger.trace("Unmarshaling as Enum: {}", arg); | ||||||
|  | 			Method cons = paramType.getDeclaredMethod("valueOf", String.class); | ||||||
|  | 			return cons.invoke(null, arg.toString()); | ||||||
|  | 		} else { | ||||||
|  | 			this.logger.trace("Unmarshaling as POJO: {}", arg); | ||||||
|  | 			try { | ||||||
|  | 				Constructor<?> cons = paramType.getConstructor(String.class); | ||||||
|  | 				return cons.newInstance(arg.toString()); | ||||||
|  | 			} catch (NoSuchMethodException nsme) { | ||||||
|  | 				Method method = paramType.getDeclaredMethod("valueOf", String.class); | ||||||
|  | 				return method.invoke(null, arg.toString()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     protected Object marshal(Object arg) { | ||||||
|  |     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { | ||||||
|  |     		return arg; | ||||||
|  |     	} else if (arg instanceof Temporal) { | ||||||
|  |     		return arg.toString(); | ||||||
|  |     	} else if (arg instanceof Version) { | ||||||
|  | 			Version version = (Version) arg; | ||||||
|  | 			Map<String, Object> map = new HashMap<>(); | ||||||
|  | 			map.put("nodeRef", version.getFrozenStateNodeRef()); | ||||||
|  | 			map.put("properties", version.getVersionProperties()); | ||||||
|  |  | ||||||
|  | 			this.logger.trace("Marshaling Version as JSON object: {}", map); | ||||||
|  | 			return this.om.convertValue(map, String.class); | ||||||
|  | 		} else if (arg instanceof Action) { | ||||||
|  | 			Action action = (Action) arg; | ||||||
|  | 			Map<String, Object> map = new HashMap<>(); | ||||||
|  | 			map.put("nodeRef", action.getNodeRef()); | ||||||
|  | 			map.put("actionId", action.getId()); | ||||||
|  |  | ||||||
|  | 			this.logger.trace("Marshaling Action as JSON object: {}", map); | ||||||
|  | 			return this.om.convertValue(map, String.class); | ||||||
|  | 		} else if (arg instanceof Collection<?>) { | ||||||
|  | 			List<Object> list = new ArrayList<>(((Collection<?>)arg).size()); | ||||||
|  | 			for (Object obj : (Collection<?>) arg) | ||||||
|  | 				list.add(this.marshal(obj)); | ||||||
|  | 			 | ||||||
|  | 			this.logger.trace("Marshaling Java Collection as JSON array: {}", list); | ||||||
|  | 			return this.om.convertValue(list, String.class); | ||||||
|  | 		} else if (arg instanceof Map<?, ?>) { | ||||||
|  | 			Map<Object, Object> map = new HashMap<>(); | ||||||
|  | 			for (Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) { | ||||||
|  | 				Object key = this.marshal(entry.getKey()); | ||||||
|  | 				Object value = this.marshal(entry.getValue()); | ||||||
|  | 				map.put(key, value); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			this.logger.trace("Marshaling Java Map as JSON object: {}", map); | ||||||
|  | 			return this.om.convertValue(map, String.class); | ||||||
|  | 		} else { | ||||||
|  | 			this.logger.trace("Marshaling Java object as JSON object: {}", arg); | ||||||
|  | 			return this.om.convertValue(arg, String.class); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     protected M2Model loadModel(NodeRef nodeRef) throws IOException { | ||||||
|  | 		ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); | ||||||
|  | 		InputStream istream = creader.getContentInputStream(); | ||||||
|  | 		try { | ||||||
|  | 			return M2Model.createModel(istream); | ||||||
|  | 		} finally { | ||||||
|  | 			istream.close(); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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,32 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import org.alfresco.util.Pair; | ||||||
|  |  | ||||||
|  | public class MapUtils { | ||||||
|  | 	 | ||||||
|  | 	public static <K, V> Map<K, V> build(Pair<K, V>... pairs) { | ||||||
|  | 		Map<K, V> map = new HashMap<>(); | ||||||
|  | 		for (Pair<K, V> pair : pairs) { | ||||||
|  | 			map.put(pair.getFirst(), pair.getSecond()); | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return map; | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public static Map<String, String> build(String... keyValuePairs) { | ||||||
|  | 		if (keyValuePairs.length % 2 == 1) | ||||||
|  | 			throw new IllegalArgumentException(); | ||||||
|  | 		 | ||||||
|  | 		Map<String, String> map = new HashMap<>(); | ||||||
|  | 		for (int pair = 0; pair < keyValuePairs.length / 2; pair++) { | ||||||
|  | 			int base = pair * 2; | ||||||
|  | 			map.put(keyValuePairs[base], keyValuePairs[base + 1]); | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		return map; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import org.springframework.beans.factory.config.ConfigurableBeanFactory; | ||||||
|  | import org.springframework.context.annotation.Scope; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  | import org.springframework.transaction.annotation.Transactional; | ||||||
|  |  | ||||||
|  | @Component | ||||||
|  | @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) | ||||||
|  | public class SpringTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public SpringTransactionalAnnotationAdapter(Transactional txl) { | ||||||
|  | 		this.txl = txl; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean isReadOnly() { | ||||||
|  | 		return this.txl.readOnly(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Propagation getPropagation() { | ||||||
|  | 		return this.txl.propagation(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Isolation getIsolation() { | ||||||
|  | 		return this.txl.isolation(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int getTimeoutInSeconds() { | ||||||
|  | 		return this.txl.timeout(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getRollbackFor() { | ||||||
|  | 		return this.txl.rollbackFor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public Class<? extends Throwable>[] getNoRollbackFor() { | ||||||
|  | 		return this.txl.noRollbackFor(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  |  | ||||||
|  | public interface TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	boolean isReadOnly(); | ||||||
|  | 	 | ||||||
|  | 	Propagation getPropagation(); | ||||||
|  | 	 | ||||||
|  | 	Isolation getIsolation(); | ||||||
|  | 	 | ||||||
|  | 	int getTimeoutInSeconds(); | ||||||
|  | 	 | ||||||
|  | 	Class<? extends Throwable>[] getRollbackFor(); | ||||||
|  | 	 | ||||||
|  | 	Class<? extends Throwable>[] getNoRollbackFor(); | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								jakarta/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								jakarta/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.1.0</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> | ||||||
|  | 	</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> | ||||||
|  | 	</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,23 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.aspect; | ||||||
|  |  | ||||||
|  | import org.aspectj.lang.annotation.Aspect; | ||||||
|  | import org.aspectj.lang.annotation.Pointcut; | ||||||
|  |  | ||||||
|  | import jakarta.transaction.Transactional; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @see jakarta.transaction.Transactional | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public String getJtaInterfaceName() { | ||||||
|  | 		return Transactional.class.getName(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(jakarta.transaction.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isJtaTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -9,6 +9,15 @@ import java.lang.reflect.Method; | |||||||
| import java.lang.reflect.Parameter; | import java.lang.reflect.Parameter; | ||||||
| import java.net.InetAddress; | import java.net.InetAddress; | ||||||
| import java.net.UnknownHostException; | 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.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| @@ -23,14 +32,7 @@ import java.util.function.Supplier; | |||||||
| import java.util.regex.Matcher; | 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; | ||||||
| @@ -48,9 +50,9 @@ 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.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.messaginghub.pooled.jms.JmsPoolConnectionFactory; | ||||||
| import org.quartz.JobKey; | import org.quartz.JobKey; | ||||||
| import org.quartz.Scheduler; | import org.quartz.Scheduler; | ||||||
| import org.quartz.SchedulerException; | import org.quartz.SchedulerException; | ||||||
| @@ -60,6 +62,8 @@ 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; | ||||||
| @@ -77,146 +81,56 @@ 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; | ||||||
|  | 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. |  * This class provides integration with MQ for the asynchronous method executions. | ||||||
|  *  |  *  | ||||||
|  * @author brian@inteligr8.com |  * @author brian@inteligr8.com | ||||||
|  */ |  */ | ||||||
| @Component("async.mq") | @Component("async.mq") | ||||||
| public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, Threadable { | public class MqAsyncService extends AbstractMqAsyncService { | ||||||
| 
 | 
 | ||||||
| 	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 Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); |  | ||||||
| 	private final ObjectMapper om = new ObjectMapper(); |  | ||||||
| 
 | 
 | ||||||
| 	@Value("${inteligr8.async.mq.enabled}") |     private JmsPoolConnectionFactory factory; | ||||||
| 	protected boolean enabled; |  | ||||||
|      |      | ||||||
| 	@Value("${inteligr8.async.mq.workerThreads}") |     /** | ||||||
| 	protected int workerThreads; |      * @PostConstruct does not work in ACS | ||||||
| 	 |      */ | ||||||
|     @Value("${inteligr8.async.mq.url}") |     @PostConstruct | ||||||
|     protected String url; |     protected void init() { | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.username}") |  | ||||||
|     protected String username; |  | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.password}") |  | ||||||
|     protected String password; |  | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.queue}") |  | ||||||
|     protected String queueName; |  | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.errorQueue}") |  | ||||||
|     protected String errorQueueName; |  | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.clientId}") |  | ||||||
|     protected String clientId; |  | ||||||
|      |  | ||||||
|     @Value("${inteligr8.async.mq.pool.max}") |  | ||||||
|     protected short maxConnections; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected ActionService actionService; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected ContentService contentService; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected CustomModelService modelService; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected DictionaryService dictionaryService; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected NamespaceService namespaceService; |  | ||||||
|      |  | ||||||
|     @Autowired |  | ||||||
|     protected TransactionService txService; |  | ||||||
|      |  | ||||||
|     private String hostname; |  | ||||||
|      |  | ||||||
|     private PooledConnectionFactory factory; |  | ||||||
|      |  | ||||||
|     private SimpleCache<Pair<Class<?>, String>, Method> methodCache; |  | ||||||
|      |  | ||||||
|     private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() { |  | ||||||
|     	@Override |  | ||||||
|     	public Boolean get() { |  | ||||||
|     		return false; |  | ||||||
|     	} |  | ||||||
| 	}); |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
|     protected void onBootstrap(ApplicationEvent event) { |  | ||||||
|     	if (!this.enabled) |     	if (!this.enabled) | ||||||
|     		return; |     		return; | ||||||
| 
 | 
 | ||||||
|     	try { | 		super.init(); | ||||||
|     		this.hostname = InetAddress.getLocalHost().getHostName(); |  | ||||||
|     	} catch (UnknownHostException uhe) { |  | ||||||
|     		this.hostname = "unknown"; |  | ||||||
|     	} |  | ||||||
|     	 |     	 | ||||||
|     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); |     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); | ||||||
|     	 |     	 | ||||||
|     	PooledConnectionFactory pool = new PooledConnectionFactory(); |     	JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory(); | ||||||
|     	pool.setConnectionFactory(factory); |     	pool.setConnectionFactory(factory); | ||||||
|     	pool.setMaxConnections(this.maxConnections); |     	pool.setMaxConnections(this.maxConnections); | ||||||
|     	pool.start(); |     	pool.start(); | ||||||
|     	 |     	 | ||||||
|     	this.factory = pool; |     	this.factory = pool; | ||||||
| 
 |  | ||||||
| 		if (this.workerThreads <= 0) |  | ||||||
| 			return; |  | ||||||
|     	 |  | ||||||
|     	JobDetailImpl jobDetail = new JobDetailImpl(); |  | ||||||
|     	jobDetail.setKey(this.jobKey); |  | ||||||
|     	jobDetail.setRequestsRecovery(true); |  | ||||||
|     	jobDetail.setJobClass(AsyncJob.class); |  | ||||||
|     	jobDetail.getJobDataMap().put("asyncService", this); |  | ||||||
|     	 |  | ||||||
|     	Trigger trigger = TriggerBuilder.newTrigger() |  | ||||||
|     			.startNow() |  | ||||||
|     			.build(); |  | ||||||
|     	 |  | ||||||
|     	try { |  | ||||||
| 	    	StdSchedulerFactory.getDefaultScheduler() |  | ||||||
| 					.scheduleJob(jobDetail, trigger); |  | ||||||
|     	} catch (SchedulerException se) { |  | ||||||
|     		this.logger.error("The MQ async service job failed to start; no asynchronous executions will be processed!", se); |  | ||||||
|     	} |  | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     @Override |     @PreDestroy | ||||||
|     protected void onShutdown(ApplicationEvent event) { |     protected void uninit() { | ||||||
|     	try { | 		super.uninit(); | ||||||
| 	    	Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); |  | ||||||
| 	    	scheduler.deleteJob(this.jobKey); |  | ||||||
|     	} catch (SchedulerException se) { |  | ||||||
|     		this.logger.warn("The MQ async service job failed to stop", se); |  | ||||||
|     	} |  | ||||||
| 
 | 
 | ||||||
|     	if (this.factory != null) |     	if (this.factory != null) | ||||||
|     		this.factory.stop(); |     		this.factory.stop(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     @Override |  | ||||||
|     public boolean isEnabled() { |  | ||||||
| 		return enabled; |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
| 	public Integer getThreads() { |  | ||||||
| 		return this.workerThreads; |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
| 	public boolean isCurrentThreadAsynchronous() { |  | ||||||
| 		return this.isAsync.get(); |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @Override |     @Override | ||||||
|     @Transactional |     @Transactional | ||||||
|     public void poll() throws AsyncProcessException { |     public void poll() throws AsyncProcessException { | ||||||
| @@ -393,43 +307,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	} |     	} | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private Method findMethod(Class<?> clazz, String methodName) { |  | ||||||
|     	Pair<Class<?>, String> key = new Pair<>(clazz, methodName); |  | ||||||
|     	Method method = this.methodCache.get(key); |  | ||||||
|     	if (method != null) { |  | ||||||
| 			this.logger.trace("Found method in cache: {}", method); |  | ||||||
|     		return method; |  | ||||||
|     	} |  | ||||||
| 
 |  | ||||||
| 		this.logger.trace("Looping through bean type methods to find: {}", methodName); |  | ||||||
| 		 |  | ||||||
| 		for (Method amethod : clazz.getDeclaredMethods()) { |  | ||||||
| 			if (amethod.getName().equals(methodName)) { |  | ||||||
| 				this.logger.debug("Found and caching method: {} => {}", key, amethod); |  | ||||||
| 				this.methodCache.put(key, amethod); |  | ||||||
| 				return amethod; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		 |  | ||||||
| 		throw new IllegalStateException("The bean (" + clazz + ") does not implement the method: " + methodName); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException { |  | ||||||
|     	this.logger.trace("push({})", joinPoint); |  | ||||||
|     	 |  | ||||||
| 		if (!(joinPoint.getSignature() instanceof MethodSignature)) |  | ||||||
| 			throw new IllegalStateException("The join point must be on methods and methods have signatures"); |  | ||||||
| 		 |  | ||||||
| 		Object bean = joinPoint.getThis(); |  | ||||||
| 		this.logger.debug("Queuing for bean: {}", bean.getClass()); |  | ||||||
| 
 |  | ||||||
| 		MethodSignature methodSig = (MethodSignature) joinPoint.getSignature(); |  | ||||||
| 		Method method = methodSig.getMethod(); |  | ||||||
| 		this.logger.debug("Queuing for method: {}", method); |  | ||||||
| 		 |  | ||||||
| 		this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs())); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @Transactional |     @Transactional | ||||||
|     public 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); |     	this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); | ||||||
| @@ -474,99 +351,4 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	} |     	} | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @SuppressWarnings({ "unchecked" }) |  | ||||||
| 	private Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { |  | ||||||
|     	Class<?> paramType = param.getType(); |  | ||||||
| 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); |  | ||||||
|     	 |  | ||||||
| 		if (Version.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as JSON object: {}", arg); |  | ||||||
| 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); |  | ||||||
| 			 |  | ||||||
| 			Map<String, Serializable> versionPropertiesMap = (Map<String, Serializable>) argMap.get("properties"); |  | ||||||
| 			NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef")); |  | ||||||
| 			 |  | ||||||
| 			Version version = new VersionImpl(versionPropertiesMap, nodeRef); |  | ||||||
| 			this.logger.trace("Unmarshaled version: {} = {}", param.getName(), version); |  | ||||||
| 			return version; |  | ||||||
| 		} else if (Action.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as JSON object: {}", arg); |  | ||||||
| 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); |  | ||||||
| 			 |  | ||||||
| 			String actionId = (String) argMap.get("actionId"); |  | ||||||
| 			NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef")); |  | ||||||
| 			this.logger.trace("Unmarshaling action: {}, {}", actionId, nodeRef); |  | ||||||
| 			 |  | ||||||
| 			Action action = this.actionService.getAction(nodeRef, actionId); |  | ||||||
| 			this.logger.trace("Unmarshaled action: {} = {}", param.getName(), action); |  | ||||||
| 			return action; |  | ||||||
| 		} else if (Collection.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as JSON array: {}", arg); |  | ||||||
| 			return this.om.convertValue(arg, Collection.class); |  | ||||||
| 		} else if (Map.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as JSON object: {}", arg); |  | ||||||
| 			return this.om.convertValue(arg, Map.class); |  | ||||||
| 		} else if (QName.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as QName: {}", arg); |  | ||||||
| 			return QName.createQName((String) arg); |  | ||||||
| 		} else if (Enum.class.isAssignableFrom(paramType)) { |  | ||||||
| 			this.logger.trace("Unmarshaling as Enum: {}", arg); |  | ||||||
| 			Method cons = paramType.getDeclaredMethod("valueOf", String.class); |  | ||||||
| 			return cons.invoke(null, arg.toString()); |  | ||||||
| 		} else { |  | ||||||
| 			this.logger.trace("Unmarshaling as POJO: {}", arg); |  | ||||||
| 			Constructor<?> cons = paramType.getConstructor(String.class); |  | ||||||
| 			return cons.newInstance(arg.toString()); |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private Object marshal(Object arg) { |  | ||||||
| 		if (arg instanceof Version) { |  | ||||||
| 			Version version = (Version) arg; |  | ||||||
| 			Map<String, Object> map = new HashMap<>(); |  | ||||||
| 			map.put("nodeRef", version.getFrozenStateNodeRef()); |  | ||||||
| 			map.put("properties", version.getVersionProperties()); |  | ||||||
| 
 |  | ||||||
| 			this.logger.trace("Marshaling Version as JSON object: {}", map); |  | ||||||
| 			return this.om.convertValue(map, String.class); |  | ||||||
| 		} else if (arg instanceof Action) { |  | ||||||
| 			Action action = (Action) arg; |  | ||||||
| 			Map<String, Object> map = new HashMap<>(); |  | ||||||
| 			map.put("nodeRef", action.getNodeRef()); |  | ||||||
| 			map.put("actionId", action.getId()); |  | ||||||
| 
 |  | ||||||
| 			this.logger.trace("Marshaling Action as JSON object: {}", map); |  | ||||||
| 			return this.om.convertValue(map, String.class); |  | ||||||
| 		} else if (arg instanceof Collection<?>) { |  | ||||||
| 			List<Object> list = new ArrayList<>(((Collection<?>)arg).size()); |  | ||||||
| 			for (Object obj : (Collection<?>) arg) |  | ||||||
| 				list.add(this.marshal(obj)); |  | ||||||
| 			 |  | ||||||
| 			this.logger.trace("Marshaling Java Collection as JSON array: {}", list); |  | ||||||
| 			return this.om.convertValue(list, String.class); |  | ||||||
| 		} else if (arg instanceof Map<?, ?>) { |  | ||||||
| 			Map<Object, Object> map = new HashMap<>(); |  | ||||||
| 			for (Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) { |  | ||||||
| 				Object key = this.marshal(entry.getKey()); |  | ||||||
| 				Object value = this.marshal(entry.getValue()); |  | ||||||
| 				map.put(key, value); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			this.logger.trace("Marshaling Java Map as JSON object: {}", map); |  | ||||||
| 			return this.om.convertValue(map, String.class); |  | ||||||
| 		} else { |  | ||||||
| 			return arg; |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private M2Model loadModel(NodeRef nodeRef) throws IOException { |  | ||||||
| 		ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); |  | ||||||
| 		InputStream istream = creader.getContentInputStream(); |  | ||||||
| 		try { |  | ||||||
| 			return M2Model.createModel(istream); |  | ||||||
| 		} finally { |  | ||||||
| 			istream.close(); |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.util; | ||||||
|  |  | ||||||
|  | import jakarta.transaction.Transactional; | ||||||
|  |  | ||||||
|  | import org.springframework.transaction.annotation.Isolation; | ||||||
|  | import org.springframework.transaction.annotation.Propagation; | ||||||
|  |  | ||||||
|  | public class JtaTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	public static final String JTA_INTERFACE_NAME = "jakarta.transaction.Transactional"; | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public static JtaTransactionalAnnotationAdapter cast(Object obj) { | ||||||
|  | 		return new JtaTransactionalAnnotationAdapter((Transactional) obj); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public JtaTransactionalAnnotationAdapter(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(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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.1.0</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,21 @@ | |||||||
|  | package com.inteligr8.alfresco.annotations.aspect; | ||||||
|  |  | ||||||
|  | import org.aspectj.lang.annotation.Aspect; | ||||||
|  | import org.aspectj.lang.annotation.Pointcut; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @see javax.transaction.Transactional | ||||||
|  |  */ | ||||||
|  | @Aspect | ||||||
|  | public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { | ||||||
|  | 	 | ||||||
|  | 	@Override | ||||||
|  | 	public String getJtaInterfaceName() { | ||||||
|  | 		return javax.transaction.Transactional.class.getName(); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | ||||||
|  | 	public void isJtaTransactionalAnnotated() { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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 JtaTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter { | ||||||
|  | 	 | ||||||
|  | 	public static final String JTA_INTERFACE_NAME = "javax.transaction.Transactional"; | ||||||
|  | 	 | ||||||
|  | 	private final Transactional txl; | ||||||
|  | 	 | ||||||
|  | 	public static JtaTransactionalAnnotationAdapter cast(Object obj) { | ||||||
|  | 		return new JtaTransactionalAnnotationAdapter((Transactional) obj); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	public JtaTransactionalAnnotationAdapter(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(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | module.id=${project.groupId}.${project.artifactId} | ||||||
|  | module.title=${project.name} | ||||||
|  | module.description=${project.description} | ||||||
|  | module.version=${project.version} | ||||||
|  |  | ||||||
|  | module.repo.version.min=6.0 | ||||||
|  | #module.repo.version.max= | ||||||
|  |  | ||||||
|  | module.depends.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.2</version> | 	<version>1.1.0</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