Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| eaa2269bf9 | |||
| e867f0d807 | |||
| 85ec7aa307 | 
							
								
								
									
										79
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -5,7 +5,7 @@ | |||||||
| 	 | 	 | ||||||
| 	<groupId>com.inteligr8.alfresco</groupId> | 	<groupId>com.inteligr8.alfresco</groupId> | ||||||
| 	<artifactId>annotations-platform-module</artifactId> | 	<artifactId>annotations-platform-module</artifactId> | ||||||
| 	<version>1.0-SNAPSHOT</version> | 	<version>1.0.1</version> | ||||||
| 	<packaging>jar</packaging> | 	<packaging>jar</packaging> | ||||||
| 	 | 	 | ||||||
| 	<name>Annotations ACS Platform Module</name> | 	<name>Annotations ACS Platform Module</name> | ||||||
| @@ -43,6 +43,8 @@ | |||||||
| 		<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.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> | ||||||
| 	</properties> | 	</properties> | ||||||
| @@ -81,32 +83,20 @@ | |||||||
| 			<version>${aspectj.version}</version> | 			<version>${aspectj.version}</version> | ||||||
| 			<scope>test</scope> | 			<scope>test</scope> | ||||||
| 		</dependency> | 		</dependency> | ||||||
|  | 		 | ||||||
|  | 		<!-- Removes startup warning regarding annotations that aren't annotations --> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>jakarta.transaction</groupId> | ||||||
|  | 			<artifactId>jakarta.transaction-api</artifactId> | ||||||
|  | 		</dependency> | ||||||
|  | 		<dependency> | ||||||
|  | 			<groupId>org.springframework</groupId> | ||||||
|  | 			<artifactId>spring-tx</artifactId> | ||||||
|  | 		</dependency> | ||||||
| 	</dependencies> | 	</dependencies> | ||||||
|  |  | ||||||
| 	<build> | 	<build> | ||||||
| 		<plugins> | 		<plugins> | ||||||
| 			<plugin> |  | ||||||
| 				<groupId>org.codehaus.mojo</groupId> |  | ||||||
| 				<artifactId>build-helper-maven-plugin</artifactId> |  | ||||||
| 				<version>3.6.0</version> |  | ||||||
| 				<executions> |  | ||||||
| 					<execution> |  | ||||||
| 						<id>add-source</id> |  | ||||||
| 						<goals><goal>add-source</goal></goals> |  | ||||||
| 						<configuration> |  | ||||||
| 							<sources> |  | ||||||
| 								<source>src/main/${source.tx.path}</source> |  | ||||||
| 							</sources> |  | ||||||
| 						</configuration> |  | ||||||
| 					</execution> |  | ||||||
| 				</executions> |  | ||||||
| 			</plugin> |  | ||||||
| 			<plugin> |  | ||||||
| 				<artifactId>maven-jar-plugin</artifactId> |  | ||||||
| 				<configuration> |  | ||||||
| 					<classifier>${classifier.tx.name}</classifier> |  | ||||||
| 				</configuration> |  | ||||||
| 			</plugin> |  | ||||||
| 			<plugin> | 			<plugin> | ||||||
| 				<groupId>io.repaint.maven</groupId> | 				<groupId>io.repaint.maven</groupId> | ||||||
| 				<artifactId>tiles-maven-plugin</artifactId> | 				<artifactId>tiles-maven-plugin</artifactId> | ||||||
| @@ -127,49 +117,6 @@ | |||||||
| 	</build> | 	</build> | ||||||
|  |  | ||||||
| 	<profiles> | 	<profiles> | ||||||
| 		<profile> |  | ||||||
| 			<id>debug</id> |  | ||||||
| 			<properties> |  | ||||||
| 				<docker.showLogs>true</docker.showLogs> |  | ||||||
| 			</properties> |  | ||||||
| 		</profile> |  | ||||||
| 		<profile> |  | ||||||
| 			<id>javax.transaction</id> |  | ||||||
| 			<activation> |  | ||||||
| 				<activeByDefault>true</activeByDefault> |  | ||||||
| 			</activation> |  | ||||||
| 			<properties> |  | ||||||
| 				<source.tx.path>javax-tx</source.tx.path> |  | ||||||
| 				<classifier.tx.name>javax_tx</classifier.tx.name> |  | ||||||
| 				<alfresco.platform.version>7.4.2</alfresco.platform.version> |  | ||||||
| 				<alfresco.platform.war.version>22.22</alfresco.platform.war.version> |  | ||||||
| 			</properties> |  | ||||||
| 			<dependencies> |  | ||||||
| 				<dependency> |  | ||||||
| 					<groupId>javax.transaction</groupId> |  | ||||||
| 					<artifactId>javax.transaction-api</artifactId> |  | ||||||
| 					<version>1.3</version> |  | ||||||
| 					<scope>provided</scope> |  | ||||||
| 				</dependency> |  | ||||||
| 			</dependencies> |  | ||||||
| 		</profile> |  | ||||||
| 		<profile> |  | ||||||
| 			<id>jakarta.transaction</id> |  | ||||||
| 			<properties> |  | ||||||
| 				<source.tx.path>jakarta-tx</source.tx.path> |  | ||||||
| 				<classifier.tx.name>jakarta_tx</classifier.tx.name> |  | ||||||
| 				<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>jakarta.transaction</groupId> |  | ||||||
| 					<artifactId>jakarta.transaction-api</artifactId> |  | ||||||
| 					<version>2.0.1</version> |  | ||||||
| 					<scope>provided</scope> |  | ||||||
| 				</dependency> |  | ||||||
| 			</dependencies> |  | ||||||
| 		</profile> |  | ||||||
| 		<profile> | 		<profile> | ||||||
| 			<id>ossrh-release</id> | 			<id>ossrh-release</id> | ||||||
| 			<properties> | 			<properties> | ||||||
|   | |||||||
| @@ -1,96 +0,0 @@ | |||||||
| 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); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,112 +0,0 @@ | |||||||
| 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); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,639 +0,0 @@ | |||||||
| package com.inteligr8.alfresco.annotations.service.impl; |  | ||||||
|  |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.InputStream; |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.lang.reflect.Constructor; |  | ||||||
| import java.lang.reflect.InvocationTargetException; |  | ||||||
| import java.lang.reflect.Method; |  | ||||||
| import java.lang.reflect.Parameter; |  | ||||||
| import java.net.InetAddress; |  | ||||||
| import java.net.UnknownHostException; |  | ||||||
| import java.time.Instant; |  | ||||||
| import java.time.LocalDate; |  | ||||||
| import java.time.LocalDateTime; |  | ||||||
| import java.time.LocalTime; |  | ||||||
| import java.time.OffsetDateTime; |  | ||||||
| import java.time.OffsetTime; |  | ||||||
| import java.time.ZonedDateTime; |  | ||||||
| import java.time.format.DateTimeFormatter; |  | ||||||
| import java.time.temporal.Temporal; |  | ||||||
| import java.util.ArrayList; |  | ||||||
| import java.util.Arrays; |  | ||||||
| import java.util.Collection; |  | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Map.Entry; |  | ||||||
| import java.util.Set; |  | ||||||
| import java.util.UUID; |  | ||||||
| import java.util.function.Supplier; |  | ||||||
| import java.util.regex.Matcher; |  | ||||||
| import java.util.regex.Pattern; |  | ||||||
|  |  | ||||||
| import org.alfresco.error.AlfrescoRuntimeException; |  | ||||||
| import org.alfresco.model.ContentModel; |  | ||||||
| import org.alfresco.repo.cache.SimpleCache; |  | ||||||
| import org.alfresco.repo.dictionary.M2Model; |  | ||||||
| import org.alfresco.repo.version.common.VersionImpl; |  | ||||||
| import org.alfresco.service.cmr.action.Action; |  | ||||||
| import org.alfresco.service.cmr.action.ActionService; |  | ||||||
| import org.alfresco.service.cmr.dictionary.CustomModelService; |  | ||||||
| import org.alfresco.service.cmr.dictionary.DictionaryService; |  | ||||||
| import org.alfresco.service.cmr.repository.ContentReader; |  | ||||||
| import org.alfresco.service.cmr.repository.ContentService; |  | ||||||
| import org.alfresco.service.cmr.repository.NodeRef; |  | ||||||
| import org.alfresco.service.cmr.version.Version; |  | ||||||
| import org.alfresco.service.namespace.NamespaceService; |  | ||||||
| import org.alfresco.service.namespace.QName; |  | ||||||
| import org.alfresco.service.transaction.TransactionService; |  | ||||||
| import org.alfresco.util.Pair; |  | ||||||
| import org.apache.activemq.ActiveMQConnectionFactory; |  | ||||||
| import org.aspectj.lang.ProceedingJoinPoint; |  | ||||||
| import org.aspectj.lang.reflect.MethodSignature; |  | ||||||
| import org.messaginghub.pooled.jms.JmsPoolConnectionFactory; |  | ||||||
| import org.quartz.JobKey; |  | ||||||
| import org.quartz.Scheduler; |  | ||||||
| import org.quartz.SchedulerException; |  | ||||||
| import org.quartz.Trigger; |  | ||||||
| import org.quartz.TriggerBuilder; |  | ||||||
| import org.quartz.impl.JobDetailImpl; |  | ||||||
| import org.quartz.impl.StdSchedulerFactory; |  | ||||||
| import org.slf4j.Logger; |  | ||||||
| import org.slf4j.LoggerFactory; |  | ||||||
| import org.springframework.beans.factory.DisposableBean; |  | ||||||
| import org.springframework.beans.factory.InitializingBean; |  | ||||||
| import org.springframework.beans.factory.annotation.Autowired; |  | ||||||
| import org.springframework.beans.factory.annotation.Value; |  | ||||||
| import org.springframework.context.ApplicationEvent; |  | ||||||
| import org.springframework.extensions.surf.util.AbstractLifecycleBean; |  | ||||||
| import org.springframework.stereotype.Component; |  | ||||||
| import org.springframework.transaction.annotation.Propagation; |  | ||||||
| import org.springframework.transaction.annotation.Transactional; |  | ||||||
|  |  | ||||||
| import com.fasterxml.jackson.databind.ObjectMapper; |  | ||||||
| import com.inteligr8.alfresco.annotations.AuthorizedAsSystem; |  | ||||||
| import com.inteligr8.alfresco.annotations.Threadable; |  | ||||||
| import com.inteligr8.alfresco.annotations.Threaded; |  | ||||||
| import com.inteligr8.alfresco.annotations.TransactionalRetryable; |  | ||||||
| import com.inteligr8.alfresco.annotations.job.AsyncJob; |  | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncProcessException; |  | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncService; |  | ||||||
|  |  | ||||||
| import jakarta.annotation.PostConstruct; |  | ||||||
| import jakarta.annotation.PreDestroy; |  | ||||||
| import jakarta.jms.Connection; |  | ||||||
| import jakarta.jms.JMSException; |  | ||||||
| import jakarta.jms.Message; |  | ||||||
| import jakarta.jms.MessageConsumer; |  | ||||||
| import jakarta.jms.MessageProducer; |  | ||||||
| import jakarta.jms.Queue; |  | ||||||
| import jakarta.jms.Session; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This class provides integration with MQ for the asynchronous method executions. |  | ||||||
|  *  |  | ||||||
|  * @author brian@inteligr8.com |  | ||||||
|  */ |  | ||||||
| @Component("async.mq") |  | ||||||
| public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable { |  | ||||||
| 	 |  | ||||||
| 	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}") |  | ||||||
| 	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; |  | ||||||
|      |  | ||||||
|     private String hostname; |  | ||||||
|      |  | ||||||
|     private JmsPoolConnectionFactory factory; |  | ||||||
|      |  | ||||||
|     private SimpleCache<Pair<Class<?>, String>, Method> methodCache; |  | ||||||
|      |  | ||||||
|     private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() { |  | ||||||
|     	@Override |  | ||||||
|     	public Boolean get() { |  | ||||||
|     		return false; |  | ||||||
|     	} |  | ||||||
| 	}); |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
|     public void afterPropertiesSet() throws Exception { |  | ||||||
|     	this.init(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
|     public 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"; |  | ||||||
|     	} |  | ||||||
|     	 |  | ||||||
|     	ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); |  | ||||||
|     	 |  | ||||||
|     	JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory(); |  | ||||||
|     	pool.setConnectionFactory(factory); |  | ||||||
|     	pool.setMaxConnections(this.maxConnections); |  | ||||||
|     	pool.start(); |  | ||||||
|     	 |  | ||||||
|     	this.factory = pool; |  | ||||||
|  |  | ||||||
| 		if (this.workerThreads <= 0) |  | ||||||
| 			throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive"); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @PreDestroy |  | ||||||
|     protected void uninit() { |  | ||||||
|     	if (this.factory != null) |  | ||||||
|     		this.factory.stop(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @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 enabled; |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
| 	public Integer getThreads() { |  | ||||||
| 		return this.workerThreads; |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
| 	public boolean isCurrentThreadAsynchronous() { |  | ||||||
| 		return this.isAsync.get(); |  | ||||||
| 	} |  | ||||||
|      |  | ||||||
|     @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(); |  | ||||||
|     	} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     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 |  | ||||||
|     public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException { |  | ||||||
|     	this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); |  | ||||||
|     	 |  | ||||||
|     	UUID msgId = UUID.randomUUID(); |  | ||||||
|     	 |  | ||||||
|     	try { |  | ||||||
| 	    	Connection mqcon = this.factory.createConnection(this.username, this.password); |  | ||||||
| 	    	try { |  | ||||||
| 		    	mqcon.setClientID(this.clientId + "-client-" + this.hostname); |  | ||||||
| 		    	 |  | ||||||
| 		    	Session mqsession = mqcon.createSession(true, Session.AUTO_ACKNOWLEDGE); |  | ||||||
| 		    	try { |  | ||||||
| 			    	this.logger.trace("Sending policy as message: {} => {}", callbackMethod, msgId); |  | ||||||
| 	 |  | ||||||
| 			    	Queue mqqueue = mqsession.createQueue(this.queueName); |  | ||||||
| 			    	 |  | ||||||
| 			    	Message mqmsg = mqsession.createMessage(); |  | ||||||
| 			    	mqmsg.setJMSMessageID(msgId.toString()); |  | ||||||
| 			    	mqmsg.setJMSType("v1:" + callbackBean.getClass() + "#" + callbackMethod); |  | ||||||
| 			    	 |  | ||||||
| 			    	int i = 0; |  | ||||||
| 			    	for (Object arg : args) |  | ||||||
| 			    		mqmsg.setObjectProperty("arg" + (i++), this.marshal(arg)); |  | ||||||
| 			    	 |  | ||||||
| 			    	MessageProducer producer = mqsession.createProducer(mqqueue); |  | ||||||
| 			    	try { |  | ||||||
| 			    		producer.send(mqmsg); |  | ||||||
| 			    	} finally { |  | ||||||
| 			    		producer.close(); |  | ||||||
| 			    	} |  | ||||||
| 	 |  | ||||||
| 			    	this.logger.debug("Sent node as message: {} => {}", callbackMethod, msgId); |  | ||||||
| 		    	} finally { |  | ||||||
| 			    	mqsession.close(); |  | ||||||
| 		    	} |  | ||||||
| 	    	} finally { |  | ||||||
| 	    		mqcon.close(); |  | ||||||
| 	    	} |  | ||||||
|     	} catch (JMSException je) { |  | ||||||
|     		throw new AsyncProcessException("A JMS messaging issue occurred", je); |  | ||||||
|     	} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @SuppressWarnings({ "unchecked" }) |  | ||||||
| 	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 (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()); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private 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); |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     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(); |  | ||||||
| 		} |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,69 +0,0 @@ | |||||||
| 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(); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,113 @@ | |||||||
|  | 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"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -8,6 +8,8 @@ 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; | ||||||
| @@ -21,8 +23,6 @@ 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()); | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| 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; | ||||||
| @@ -18,12 +17,9 @@ 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.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.util.JtaTransactionalAnnotationAdapter; | import com.inteligr8.alfresco.annotations.TransactionalWrapper; | ||||||
| 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 | ||||||
| @@ -54,7 +50,7 @@ public class RetryingTransactionAspect { | |||||||
| 	public void isTransactionalAnnotated() { | 	public void isTransactionalAnnotated() { | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@Pointcut("@annotation(" + JtaTransactionalAnnotationAdapter.JTA_INTERFACE_NAME + ") && execution(* *(..))") | 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | ||||||
| 	public void isJtaTransactionalAnnotated() { | 	public void isJtaTransactionalAnnotated() { | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| @@ -67,7 +63,7 @@ public class RetryingTransactionAspect { | |||||||
| 		this.logger.trace("retryingTransactional({})", joinPoint); | 		this.logger.trace("retryingTransactional({})", joinPoint); | ||||||
| 		 | 		 | ||||||
| 		Method method = this.getMethod(joinPoint); | 		Method method = this.getMethod(joinPoint); | ||||||
| 		TransactionalAnnotationAdapter txl = this.wrapTransactionalAnnotation(method); | 		TransactionalWrapper txl = TransactionalWrapper.wrap(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)) { | ||||||
| @@ -81,29 +77,6 @@ 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, JtaTransactionalAnnotationAdapter.JTA_INTERFACE_NAME); |  | ||||||
| 		if (txl != null) |  | ||||||
| 			return JtaTransactionalAnnotationAdapter.cast(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"); | ||||||
| @@ -112,7 +85,7 @@ public class RetryingTransactionAspect { | |||||||
| 		return methodSig.getMethod(); | 		return methodSig.getMethod(); | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private boolean isReadStateChange(TransactionalAnnotationAdapter txl) { | 	private boolean isReadStateChange(TransactionalWrapper txl) { | ||||||
| 		if (txl == null) | 		if (txl == null) | ||||||
| 			return false; | 			return false; | ||||||
| 		 | 		 | ||||||
| @@ -141,7 +114,7 @@ public class RetryingTransactionAspect { | |||||||
| 		return txtry != null; | 		return txtry != null; | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private boolean doCreateNewTxContext(TransactionalAnnotationAdapter txl) { | 	private boolean doCreateNewTxContext(TransactionalWrapper txl) { | ||||||
| 		if (txl == null) { | 		if (txl == null) { | ||||||
| 			return false; | 			return false; | ||||||
| 		} else switch (txl.getPropagation()) { | 		} else switch (txl.getPropagation()) { | ||||||
| @@ -163,15 +136,8 @@ 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()) { | ||||||
| @@ -187,7 +153,7 @@ public class RetryingTransactionAspect { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	private Object execute(final ProceedingJoinPoint joinPoint, TransactionalAnnotationAdapter txl, TransactionalRetryable txtry) throws Throwable { | 	private Object execute(final ProceedingJoinPoint joinPoint, TransactionalWrapper 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 { | ||||||
|   | |||||||
| @@ -9,15 +9,6 @@ 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; | ||||||
| @@ -40,7 +31,6 @@ import javax.jms.MessageProducer; | |||||||
| import javax.jms.Queue; | import javax.jms.Queue; | ||||||
| import javax.jms.Session; | import javax.jms.Session; | ||||||
| 
 | 
 | ||||||
| import org.alfresco.error.AlfrescoRuntimeException; |  | ||||||
| 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; | ||||||
| @@ -70,8 +60,6 @@ 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; | ||||||
| @@ -89,16 +77,13 @@ import com.inteligr8.alfresco.annotations.job.AsyncJob; | |||||||
| import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||||
| import com.inteligr8.alfresco.annotations.service.AsyncService; | import com.inteligr8.alfresco.annotations.service.AsyncService; | ||||||
| 
 | 
 | ||||||
| import jakarta.annotation.PostConstruct; |  | ||||||
| import jakarta.annotation.PreDestroy; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This class provides integration with MQ for the asynchronous method executions. |  * 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, InitializingBean, DisposableBean, Threadable { | public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, Threadable { | ||||||
| 	 | 	 | ||||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||||
| 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | ||||||
| @@ -164,20 +149,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 	}); | 	}); | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     public void afterPropertiesSet() throws Exception { |     protected void onBootstrap(ApplicationEvent event) { | ||||||
|     	this.init(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
|     public void destroy() throws Exception { |  | ||||||
|     	this.uninit(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     /** |  | ||||||
|      * @PostConstruct does not work in ACS |  | ||||||
|      */ |  | ||||||
|     @PostConstruct |  | ||||||
|     protected void init() { |  | ||||||
|     	if (!this.enabled) |     	if (!this.enabled) | ||||||
|     		return; |     		return; | ||||||
|     	 |     	 | ||||||
| @@ -197,18 +169,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	this.factory = pool; |     	this.factory = pool; | ||||||
| 
 | 
 | ||||||
| 		if (this.workerThreads <= 0) | 		if (this.workerThreads <= 0) | ||||||
| 			throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive"); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @PreDestroy |  | ||||||
|     protected void uninit() { |  | ||||||
|     	if (this.factory != null) |  | ||||||
|     		this.factory.stop(); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     @Override |  | ||||||
|     protected void onBootstrap(ApplicationEvent event) { |  | ||||||
|     	if (!this.enabled) |  | ||||||
| 			return; | 			return; | ||||||
|     	 |     	 | ||||||
|     	JobDetailImpl jobDetail = new JobDetailImpl(); |     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||||
| @@ -237,6 +197,9 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	} catch (SchedulerException se) { |     	} catch (SchedulerException se) { | ||||||
|     		this.logger.warn("The MQ async service job failed to stop", se); |     		this.logger.warn("The MQ async service job failed to stop", se); | ||||||
|     	} |     	} | ||||||
|  |     	 | ||||||
|  |     	if (this.factory != null) | ||||||
|  |     		this.factory.stop(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
| @@ -516,28 +479,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
|     	Class<?> paramType = param.getType(); |     	Class<?> paramType = param.getType(); | ||||||
| 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); | 		this.logger.trace("Unmarshaling parameter of type: {}", paramType); | ||||||
|     	 |     	 | ||||||
|     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { | 		if (Version.class.isAssignableFrom(paramType)) { | ||||||
| 			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); | 			this.logger.trace("Unmarshaling as JSON object: {}", arg); | ||||||
| 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); | 			Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class); | ||||||
| 			 | 			 | ||||||
| @@ -573,22 +515,13 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 			return cons.invoke(null, arg.toString()); | 			return cons.invoke(null, arg.toString()); | ||||||
| 		} else { | 		} else { | ||||||
| 			this.logger.trace("Unmarshaling as POJO: {}", arg); | 			this.logger.trace("Unmarshaling as POJO: {}", arg); | ||||||
| 			try { |  | ||||||
| 			Constructor<?> cons = paramType.getConstructor(String.class); | 			Constructor<?> cons = paramType.getConstructor(String.class); | ||||||
| 			return cons.newInstance(arg.toString()); | 			return cons.newInstance(arg.toString()); | ||||||
| 			} catch (NoSuchMethodException nsme) { |  | ||||||
| 				Method method = paramType.getDeclaredMethod("valueOf", String.class); |  | ||||||
| 				return method.invoke(null, arg.toString()); |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private Object marshal(Object arg) { |     private Object marshal(Object arg) { | ||||||
|     	if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { | 		if (arg instanceof Version) { | ||||||
|     		return arg; |  | ||||||
|     	} else if (arg instanceof Temporal) { |  | ||||||
|     		return arg.toString(); |  | ||||||
|     	} else if (arg instanceof Version) { |  | ||||||
| 			Version version = (Version) arg; | 			Version version = (Version) arg; | ||||||
| 			Map<String, Object> map = new HashMap<>(); | 			Map<String, Object> map = new HashMap<>(); | ||||||
| 			map.put("nodeRef", version.getFrozenStateNodeRef()); | 			map.put("nodeRef", version.getFrozenStateNodeRef()); | ||||||
| @@ -622,8 +555,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | |||||||
| 			this.logger.trace("Marshaling Java Map as JSON object: {}", map); | 			this.logger.trace("Marshaling Java Map as JSON object: {}", map); | ||||||
| 			return this.om.convertValue(map, String.class); | 			return this.om.convertValue(map, String.class); | ||||||
| 		} else { | 		} else { | ||||||
| 			this.logger.trace("Marshaling Java object as JSON object: {}", arg); | 			return arg; | ||||||
| 			return this.om.convertValue(arg, String.class); |  | ||||||
| 		} | 		} | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -22,7 +22,6 @@ 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; | ||||||
| @@ -33,8 +32,6 @@ 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. | ||||||
| @@ -42,7 +39,7 @@ import jakarta.annotation.PostConstruct; | |||||||
|  * @author brian@inteligr8.com |  * @author brian@inteligr8.com | ||||||
|  */ |  */ | ||||||
| @Component("async.thread") | @Component("async.thread") | ||||||
| public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean { | public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService { | ||||||
| 	 | 	 | ||||||
| 	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"); | ||||||
| @@ -64,19 +61,9 @@ public class ThreadPoolAsyncService extends AbstractLifecycleBean implements Asy | |||||||
| 	}); | 	}); | ||||||
|      |      | ||||||
|     @Override |     @Override | ||||||
|     public void afterPropertiesSet() throws Exception { |  | ||||||
|     	this.init(); |  | ||||||
| 	} |  | ||||||
| 	 |  | ||||||
| 	/** |  | ||||||
| 	 * @PostConstruct doesn't work in ACS for whatever reason |  | ||||||
| 	 */ |  | ||||||
| 	@PostConstruct |  | ||||||
| 	protected void init() { |  | ||||||
|     	this.queue = new LinkedBlockingQueue<>(this.queueSize); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     protected void onBootstrap(ApplicationEvent event) { |     protected void onBootstrap(ApplicationEvent event) { | ||||||
|  |     	this.queue = new LinkedBlockingQueue<>(this.queueSize); | ||||||
|  |     	 | ||||||
|     	JobDetailImpl jobDetail = new JobDetailImpl(); |     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||||
|     	jobDetail.setKey(this.jobKey); |     	jobDetail.setKey(this.jobKey); | ||||||
|     	jobDetail.setRequestsRecovery(true); |     	jobDetail.setRequestsRecovery(true); | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| 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; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,45 +0,0 @@ | |||||||
| package com.inteligr8.alfresco.annotations.util; |  | ||||||
|  |  | ||||||
| import org.springframework.transaction.annotation.Isolation; |  | ||||||
| import org.springframework.transaction.annotation.Propagation; |  | ||||||
| import org.springframework.transaction.annotation.Transactional; |  | ||||||
|  |  | ||||||
| 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(); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| 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(); |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,69 +0,0 @@ | |||||||
| 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(); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -44,8 +44,9 @@ public class TransactionalTest extends AbstractLifecycleBean { | |||||||
| 		 | 		 | ||||||
| 		try { | 		try { | ||||||
| 			this.tryNoSupportsTransactional(); | 			this.tryNoSupportsTransactional(); | ||||||
| 		} catch (IllegalTransactionStateException uoe) { |  | ||||||
| 			throw new IllegalStateException(); | 			throw new IllegalStateException(); | ||||||
|  | 		} catch (IllegalTransactionStateException uoe) { | ||||||
|  | 			// suppress | ||||||
| 		} | 		} | ||||||
| 		 | 		 | ||||||
| 		try { | 		try { | ||||||
| @@ -199,8 +200,7 @@ public class TransactionalTest extends AbstractLifecycleBean { | |||||||
| 	 | 	 | ||||||
| 	@Transactional(propagation = Propagation.NOT_SUPPORTED) | 	@Transactional(propagation = Propagation.NOT_SUPPORTED) | ||||||
| 	private void tryNoSupportsTransactional() { | 	private void tryNoSupportsTransactional() { | ||||||
| 		Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction"); | 		throw new UnsupportedOperationException(); | ||||||
| 		Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction"); |  | ||||||
| 	} | 	} | ||||||
| 	 | 	 | ||||||
| 	@Transactional(propagation = Propagation.NEVER) | 	@Transactional(propagation = Propagation.NEVER) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user