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> | ||||
| 	<artifactId>annotations-platform-module</artifactId> | ||||
| 	<version>1.0-SNAPSHOT</version> | ||||
| 	<version>1.0.1</version> | ||||
| 	<packaging>jar</packaging> | ||||
| 	 | ||||
| 	<name>Annotations ACS Platform Module</name> | ||||
| @@ -43,6 +43,8 @@ | ||||
| 		<maven.compiler.target>8</maven.compiler.target> | ||||
|  | ||||
| 		<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> | ||||
| 		<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts> | ||||
| 	</properties> | ||||
| @@ -81,32 +83,20 @@ | ||||
| 			<version>${aspectj.version}</version> | ||||
| 			<scope>test</scope> | ||||
| 		</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> | ||||
|  | ||||
| 	<build> | ||||
| 		<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> | ||||
| 				<groupId>io.repaint.maven</groupId> | ||||
| 				<artifactId>tiles-maven-plugin</artifactId> | ||||
| @@ -127,49 +117,6 @@ | ||||
| 	</build> | ||||
|  | ||||
| 	<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> | ||||
| 			<id>ossrh-release</id> | ||||
| 			<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.Set; | ||||
|  | ||||
| import javax.annotation.PostConstruct; | ||||
|  | ||||
| import org.alfresco.repo.cache.DefaultSimpleCache; | ||||
| import org.alfresco.repo.cache.SimpleCache; | ||||
| import org.alfresco.service.cmr.repository.AssociationRef; | ||||
| @@ -21,8 +23,6 @@ import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
|  | ||||
| import jakarta.annotation.PostConstruct; | ||||
|  | ||||
| public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> { | ||||
| 	 | ||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package com.inteligr8.alfresco.annotations.aspect; | ||||
|  | ||||
| import java.lang.annotation.Annotation; | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport; | ||||
| @@ -18,12 +17,9 @@ import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.transaction.IllegalTransactionStateException; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import com.inteligr8.alfresco.annotations.TransactionalRetryable; | ||||
| import com.inteligr8.alfresco.annotations.util.JtaTransactionalAnnotationAdapter; | ||||
| import com.inteligr8.alfresco.annotations.util.SpringTransactionalAnnotationAdapter; | ||||
| import com.inteligr8.alfresco.annotations.util.TransactionalAnnotationAdapter; | ||||
| import com.inteligr8.alfresco.annotations.TransactionalWrapper; | ||||
|  | ||||
| /** | ||||
|  * This aspect implements the @Transactional and @TransactionalRetryable | ||||
| @@ -54,7 +50,7 @@ public class RetryingTransactionAspect { | ||||
| 	public void isTransactionalAnnotated() { | ||||
| 	} | ||||
| 	 | ||||
| 	@Pointcut("@annotation(" + JtaTransactionalAnnotationAdapter.JTA_INTERFACE_NAME + ") && execution(* *(..))") | ||||
| 	@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") | ||||
| 	public void isJtaTransactionalAnnotated() { | ||||
| 	} | ||||
| 	 | ||||
| @@ -67,7 +63,7 @@ public class RetryingTransactionAspect { | ||||
| 		this.logger.trace("retryingTransactional({})", joinPoint); | ||||
| 		 | ||||
| 		Method method = this.getMethod(joinPoint); | ||||
| 		TransactionalAnnotationAdapter txl = this.wrapTransactionalAnnotation(method); | ||||
| 		TransactionalWrapper txl = TransactionalWrapper.wrap(method); | ||||
| 		TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class); | ||||
| 		 | ||||
| 		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) { | ||||
| 		if (!(joinPoint.getSignature() instanceof MethodSignature)) | ||||
| 			throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods"); | ||||
| @@ -112,7 +85,7 @@ public class RetryingTransactionAspect { | ||||
| 		return methodSig.getMethod(); | ||||
| 	} | ||||
| 	 | ||||
| 	private boolean isReadStateChange(TransactionalAnnotationAdapter txl) { | ||||
| 	private boolean isReadStateChange(TransactionalWrapper txl) { | ||||
| 		if (txl == null) | ||||
| 			return false; | ||||
| 		 | ||||
| @@ -141,7 +114,7 @@ public class RetryingTransactionAspect { | ||||
| 		return txtry != null; | ||||
| 	} | ||||
| 	 | ||||
| 	private boolean doCreateNewTxContext(TransactionalAnnotationAdapter txl) { | ||||
| 	private boolean doCreateNewTxContext(TransactionalWrapper txl) { | ||||
| 		if (txl == null) { | ||||
| 			return false; | ||||
| 		} else switch (txl.getPropagation()) { | ||||
| @@ -163,15 +136,8 @@ public class RetryingTransactionAspect { | ||||
| 						if (txl.isReadOnly()) | ||||
| 							throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory"); | ||||
| 				} | ||||
| 				return false; | ||||
| 			case NOT_SUPPORTED: | ||||
| 				switch (AlfrescoTransactionSupport.getTransactionReadState()) { | ||||
| 					case TXN_NONE: | ||||
| 						return false; | ||||
| 					default: | ||||
| 						throw new IllegalTransactionStateException("A transaction exists and pausing it is not supported"); | ||||
| 				} | ||||
| 			case SUPPORTS: | ||||
| 			//case NOT_SUPPORTED:  not supported; we would have to create another thread to simulate | ||||
| 				return false; | ||||
| 			case REQUIRED: | ||||
| 				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>() { | ||||
| 			@Override | ||||
| 			public Object execute() throws Throwable { | ||||
|   | ||||
| @@ -9,15 +9,6 @@ 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; | ||||
| @@ -40,7 +31,6 @@ import javax.jms.MessageProducer; | ||||
| import javax.jms.Queue; | ||||
| import javax.jms.Session; | ||||
| 
 | ||||
| import org.alfresco.error.AlfrescoRuntimeException; | ||||
| import org.alfresco.model.ContentModel; | ||||
| import org.alfresco.repo.cache.SimpleCache; | ||||
| import org.alfresco.repo.dictionary.M2Model; | ||||
| @@ -70,8 +60,6 @@ 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; | ||||
| @@ -89,16 +77,13 @@ 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; | ||||
| 
 | ||||
| /** | ||||
|  * 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 { | ||||
| public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, Threadable { | ||||
| 	 | ||||
| 	private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||||
| 	private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); | ||||
| @@ -164,20 +149,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | ||||
| 	}); | ||||
|      | ||||
|     @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() { | ||||
|     protected void onBootstrap(ApplicationEvent event) { | ||||
|     	if (!this.enabled) | ||||
|     		return; | ||||
|     	 | ||||
| @@ -197,18 +169,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | ||||
|     	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(); | ||||
| @@ -237,6 +197,9 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | ||||
|     	} catch (SchedulerException se) { | ||||
|     		this.logger.warn("The MQ async service job failed to stop", se); | ||||
|     	} | ||||
|     	 | ||||
|     	if (this.factory != null) | ||||
|     		this.factory.stop(); | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
| @@ -516,28 +479,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | ||||
|     	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)) { | ||||
| 		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); | ||||
| 			 | ||||
| @@ -573,22 +515,13 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic | ||||
| 			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) { | ||||
| 		if (arg instanceof Version) { | ||||
| 			Version version = (Version) arg; | ||||
| 			Map<String, Object> map = new HashMap<>(); | ||||
| 			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); | ||||
| 			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); | ||||
| 			return arg; | ||||
| 		} | ||||
|     } | ||||
|      | ||||
| @@ -22,7 +22,6 @@ import org.quartz.impl.JobDetailImpl; | ||||
| import org.quartz.impl.StdSchedulerFactory; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.InitializingBean; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.context.ApplicationEvent; | ||||
| import org.springframework.extensions.surf.util.AbstractLifecycleBean; | ||||
| @@ -33,8 +32,6 @@ import com.inteligr8.alfresco.annotations.job.AsyncJob; | ||||
| import com.inteligr8.alfresco.annotations.service.AsyncProcessException; | ||||
| import com.inteligr8.alfresco.annotations.service.AsyncService; | ||||
|  | ||||
| import jakarta.annotation.PostConstruct; | ||||
|  | ||||
| /** | ||||
|  * This class provides a non-persistent alternative to MQ for asynchronous method | ||||
|  * execution. | ||||
| @@ -42,7 +39,7 @@ import jakarta.annotation.PostConstruct; | ||||
|  * @author brian@inteligr8.com | ||||
|  */ | ||||
| @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 JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations"); | ||||
| @@ -64,19 +61,9 @@ public class ThreadPoolAsyncService extends AbstractLifecycleBean implements Asy | ||||
| 	}); | ||||
|      | ||||
|     @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) { | ||||
|     	this.queue = new LinkedBlockingQueue<>(this.queueSize); | ||||
|     	 | ||||
|     	JobDetailImpl jobDetail = new JobDetailImpl(); | ||||
|     	jobDetail.setKey(this.jobKey); | ||||
|     	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 { | ||||
| 			this.tryNoSupportsTransactional(); | ||||
| 		} catch (IllegalTransactionStateException uoe) { | ||||
| 			throw new IllegalStateException(); | ||||
| 		} catch (IllegalTransactionStateException uoe) { | ||||
| 			// suppress | ||||
| 		} | ||||
| 		 | ||||
| 		try { | ||||
| @@ -199,8 +200,7 @@ public class TransactionalTest extends AbstractLifecycleBean { | ||||
| 	 | ||||
| 	@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"); | ||||
| 		throw new UnsupportedOperationException(); | ||||
| 	} | ||||
| 	 | ||||
| 	@Transactional(propagation = Propagation.NEVER) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user