diff --git a/metadata.keystore b/core/metadata.keystore similarity index 100% rename from metadata.keystore rename to core/metadata.keystore diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..5f552ee --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,40 @@ + + 4.0.0 + + + com.inteligr8.alfresco + annotations-platform-module + 1.0-SNAPSHOT + ../ + + + annotations-core-platform-module + jar + + + 22.22 + + + + + + io.repaint.maven + tiles-maven-plugin + 2.40 + true + + + + com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0) + + + + + + \ No newline at end of file diff --git a/rad.ps1 b/core/rad.ps1 similarity index 100% rename from rad.ps1 rename to core/rad.ps1 diff --git a/rad.sh b/core/rad.sh similarity index 100% rename from rad.sh rename to core/rad.sh diff --git a/src/main/java/com/inteligr8/alfresco/annotations/AssociationTypeConstrainable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/AssociationTypeConstrainable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/AssociationTypeConstrainable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/AssociationTypeConstrainable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/Asynchronous.java b/core/src/main/java/com/inteligr8/alfresco/annotations/Asynchronous.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/Asynchronous.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/Asynchronous.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/Authorizable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/Authorizable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/Authorizable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/Authorizable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/Authorized.java b/core/src/main/java/com/inteligr8/alfresco/annotations/Authorized.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/Authorized.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/Authorized.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/AuthorizedAsSystem.java b/core/src/main/java/com/inteligr8/alfresco/annotations/AuthorizedAsSystem.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/AuthorizedAsSystem.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/AuthorizedAsSystem.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/IfChildAssociationIsPrimary.java b/core/src/main/java/com/inteligr8/alfresco/annotations/IfChildAssociationIsPrimary.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/IfChildAssociationIsPrimary.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/IfChildAssociationIsPrimary.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/IfNodeExists.java b/core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeExists.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/IfNodeExists.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeExists.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/IfNodeHasAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeHasAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/IfNodeHasAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeHasAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/IfNodeOfType.java b/core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeOfType.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/IfNodeOfType.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/IfNodeOfType.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/IfNotNull.java b/core/src/main/java/com/inteligr8/alfresco/annotations/IfNotNull.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/IfNotNull.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/IfNotNull.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/JobSynchronized.java b/core/src/main/java/com/inteligr8/alfresco/annotations/JobSynchronized.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/JobSynchronized.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/JobSynchronized.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/NodeAspectConstrainable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/NodeAspectConstrainable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/NodeAspectConstrainable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/NodeAspectConstrainable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/NodeTypeConstrainable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/NodeTypeConstrainable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/NodeTypeConstrainable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/NodeTypeConstrainable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/Threadable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/Threadable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/Threadable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/Threadable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/Threaded.java b/core/src/main/java/com/inteligr8/alfresco/annotations/Threaded.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/Threaded.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/Threaded.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/TransactionalRetryable.java b/core/src/main/java/com/inteligr8/alfresco/annotations/TransactionalRetryable.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/TransactionalRetryable.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/TransactionalRetryable.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodOrParameterAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodOrParameterAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodOrParameterAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractMethodOrParameterAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractRetryingTransactionAspect.java similarity index 95% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractRetryingTransactionAspect.java index 6950d8d..f4d9859 100644 --- a/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java +++ b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractRetryingTransactionAspect.java @@ -10,13 +10,13 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti import org.alfresco.service.transaction.TransactionService; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.DeclarePrecedence; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.transaction.IllegalTransactionStateException; import org.springframework.transaction.annotation.Transactional; @@ -41,22 +41,24 @@ import com.inteligr8.alfresco.annotations.util.TransactionalAnnotationAdapter; * @see org.springframework.transaction.annotation.Transactional * @see com.inteligr8.alfresco.annotations.TransactionalRetryable */ -@Aspect @DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect") -public class RetryingTransactionAspect { +public abstract class AbstractRetryingTransactionAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass()); + @Autowired + private ApplicationContext context; + @Autowired private TransactionService txService; + public abstract String getJtaInterfaceName(); + @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))") public void isTransactionalAnnotated() { } - @Pointcut("@annotation(" + JtaTransactionalAnnotationAdapter.JTA_INTERFACE_NAME + ") && execution(* *(..))") - public void isJtaTransactionalAnnotated() { - } + public abstract void isJtaTransactionalAnnotated(); @Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))") public void isTransactionalRetryableAnnotated() { @@ -86,9 +88,9 @@ public class RetryingTransactionAspect { if (txl != null) return new SpringTransactionalAnnotationAdapter((Transactional) txl); - txl = this.getOptionalAnnotation(method, JtaTransactionalAnnotationAdapter.JTA_INTERFACE_NAME); + txl = this.getOptionalAnnotation(method, this.getJtaInterfaceName()); if (txl != null) - return JtaTransactionalAnnotationAdapter.cast(txl); + return this.context.getAutowireCapableBeanFactory().getBean(JtaTransactionalAnnotationAdapter.class, txl); return null; } diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractWarnOnceService.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractWarnOnceService.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractWarnOnceService.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AbstractWarnOnceService.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/ApplicableParameterCallback.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ApplicableParameterCallback.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/ApplicableParameterCallback.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ApplicableParameterCallback.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/AsyncAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AsyncAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/AsyncAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AsyncAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/AuthorizedAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AuthorizedAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/AuthorizedAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/AuthorizedAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/ChildIsPrimaryAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ChildIsPrimaryAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/ChildIsPrimaryAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ChildIsPrimaryAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/JobLockAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/JobLockAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/JobLockAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/JobLockAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeAspectAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeAspectAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeAspectAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeAspectAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeTypeAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeTypeAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeTypeAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NodeTypeAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/NotNullAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NotNullAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/NotNullAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/NotNullAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/OperableNodeAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/OperableNodeAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/OperableNodeAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/OperableNodeAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/QNameBasedAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/QNameBasedAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/QNameBasedAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/QNameBasedAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/aspect/ThreadedAspect.java b/core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ThreadedAspect.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/aspect/ThreadedAspect.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/aspect/ThreadedAspect.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/job/AsyncJob.java b/core/src/main/java/com/inteligr8/alfresco/annotations/job/AsyncJob.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/job/AsyncJob.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/job/AsyncJob.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncProcessException.java b/core/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncProcessException.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/service/AsyncProcessException.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncProcessException.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncService.java b/core/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncService.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/service/AsyncService.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/service/AsyncService.java diff --git a/src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java b/core/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AbstractMqAsyncService.java similarity index 57% rename from src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AbstractMqAsyncService.java index 339f8c1..ffc02f3 100644 --- a/src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java +++ b/core/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AbstractMqAsyncService.java @@ -22,24 +22,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; import java.util.function.Supplier; -import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.jms.Connection; -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageConsumer; -import javax.jms.MessageProducer; -import javax.jms.Queue; -import javax.jms.Session; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; @@ -57,8 +45,6 @@ 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.apache.activemq.jms.pool.PooledConnectionFactory; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.quartz.JobKey; @@ -76,15 +62,8 @@ 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; @@ -93,17 +72,14 @@ 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 abstract class AbstractMqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations"); - private final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); - private final ObjectMapper om = new ObjectMapper(); + protected final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)"); + protected final ObjectMapper om = new ObjectMapper(); @Value("${inteligr8.async.mq.enabled}") protected boolean enabled; @@ -150,13 +126,11 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic @Autowired protected TransactionService txService; - private String hostname; + protected String hostname; - private PooledConnectionFactory factory; + protected SimpleCache, String>, Method> methodCache; - private SimpleCache, String>, Method> methodCache; - - private ThreadLocal isAsync = ThreadLocal.withInitial(new Supplier() { + protected ThreadLocal isAsync = ThreadLocal.withInitial(new Supplier() { @Override public Boolean get() { return false; @@ -164,12 +138,12 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic }); @Override - public void afterPropertiesSet() throws Exception { + public final void afterPropertiesSet() throws Exception { this.init(); } @Override - public void destroy() throws Exception { + public final void destroy() throws Exception { this.uninit(); } @@ -186,15 +160,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic } catch (UnknownHostException uhe) { this.hostname = "unknown"; } - - ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); - - PooledConnectionFactory pool = new PooledConnectionFactory(); - pool.setConnectionFactory(factory); - pool.setMaxConnections(this.maxConnections); - pool.start(); - - this.factory = pool; if (this.workerThreads <= 0) throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive"); @@ -202,8 +167,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic @PreDestroy protected void uninit() { - if (this.factory != null) - this.factory.stop(); } @Override @@ -241,7 +204,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic @Override public boolean isEnabled() { - return enabled; + return this.enabled; } @Override @@ -254,183 +217,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic 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 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 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) { + protected Method findMethod(Class clazz, String methodName) { Pair, String> key = new Pair<>(clazz, methodName); Method method = this.methodCache.get(key); if (method != null) { @@ -466,53 +253,11 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs())); } - - @Transactional - public void push(Object callbackBean, String callbackMethod, List 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); - } - } + + public abstract void push(Object callbackBean, String callbackMethod, List args) throws AsyncProcessException; @SuppressWarnings({ "unchecked" }) - private Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + protected Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class paramType = param.getType(); this.logger.trace("Unmarshaling parameter of type: {}", paramType); @@ -583,7 +328,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic } } - private Object marshal(Object arg) { + protected Object marshal(Object arg) { if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) { return arg; } else if (arg instanceof Temporal) { @@ -627,7 +372,7 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic } } - private M2Model loadModel(NodeRef nodeRef) throws IOException { + protected M2Model loadModel(NodeRef nodeRef) throws IOException { ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); InputStream istream = creader.getContentInputStream(); try { diff --git a/src/main/java/com/inteligr8/alfresco/annotations/service/impl/ThreadPoolAsyncService.java b/core/src/main/java/com/inteligr8/alfresco/annotations/service/impl/ThreadPoolAsyncService.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/service/impl/ThreadPoolAsyncService.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/service/impl/ThreadPoolAsyncService.java diff --git a/core/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java b/core/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java new file mode 100644 index 0000000..190798e --- /dev/null +++ b/core/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java @@ -0,0 +1,26 @@ +package com.inteligr8.alfresco.annotations.util; + +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; + +public interface JtaTransactionalAnnotationAdapter extends TransactionalAnnotationAdapter { + + default boolean isReadOnly() { + return false; + } + + Propagation getPropagation(); + + default Isolation getIsolation() { + return Isolation.DEFAULT; + } + + default int getTimeoutInSeconds() { + return 0; + } + + Class[] getRollbackFor(); + + Class[] getNoRollbackFor(); + +} diff --git a/src/main/java/com/inteligr8/alfresco/annotations/util/MapUtils.java b/core/src/main/java/com/inteligr8/alfresco/annotations/util/MapUtils.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/util/MapUtils.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/util/MapUtils.java diff --git a/src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java b/core/src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java similarity index 80% rename from src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java index acc4fcb..3ee603f 100644 --- a/src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java +++ b/core/src/main/java/com/inteligr8/alfresco/annotations/util/SpringTransactionalAnnotationAdapter.java @@ -1,9 +1,14 @@ package com.inteligr8.alfresco.annotations.util; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +@Component +@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class SpringTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter { private final Transactional txl; diff --git a/src/main/java/com/inteligr8/alfresco/annotations/util/TransactionalAnnotationAdapter.java b/core/src/main/java/com/inteligr8/alfresco/annotations/util/TransactionalAnnotationAdapter.java similarity index 100% rename from src/main/java/com/inteligr8/alfresco/annotations/util/TransactionalAnnotationAdapter.java rename to core/src/main/java/com/inteligr8/alfresco/annotations/util/TransactionalAnnotationAdapter.java diff --git a/src/main/resources/META-INF/aop.xml b/core/src/main/resources/META-INF/aop.xml similarity index 100% rename from src/main/resources/META-INF/aop.xml rename to core/src/main/resources/META-INF/aop.xml diff --git a/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/alfresco-global.properties b/core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/alfresco-global.properties similarity index 100% rename from src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/alfresco-global.properties rename to core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/alfresco-global.properties diff --git a/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j.properties b/core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j.properties similarity index 100% rename from src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j.properties rename to core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j.properties diff --git a/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j2.properties b/core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j2.properties similarity index 100% rename from src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j2.properties rename to core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/log4j2.properties diff --git a/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module-context.xml b/core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module-context.xml similarity index 100% rename from src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module-context.xml rename to core/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module-context.xml diff --git a/core/src/test/java/com/inteligr8/alfresco/annotations/AbstractTransactionalTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/AbstractTransactionalTest.java new file mode 100644 index 0000000..5b53d50 --- /dev/null +++ b/core/src/test/java/com/inteligr8/alfresco/annotations/AbstractTransactionalTest.java @@ -0,0 +1,222 @@ +package com.inteligr8.alfresco.annotations; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +public class AbstractTransactionalTest extends AbstractLifecycleBean { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected void onBootstrap(ApplicationEvent event) { + this.logger.info("Running test: " + this.getClass()); + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "An unexpected transaction: " + AlfrescoTransactionSupport.getTransactionId()); + + this.tryOutsideTx(); + this.tryWithinTx(); + this.tryWithinReadonlyTx(); + } + + @Override + protected void onShutdown(ApplicationEvent event) { + } + + private void tryOutsideTx() { + this.logger.info("Running outside TX test"); + + this.tryDefaultTransactional(null, false); + this.tryReadOnlyTransactional(null, false); + this.tryRetryOnlyTransactional(null); + this.trySupportsTransactional(null, false); + this.tryRequiresNewTransactional(null, false); + this.tryRequiredTransactional(null, false); + this.tryNeverTransactional(null); + + try { + this.tryNoSupportsTransactional(); + } catch (IllegalTransactionStateException uoe) { + throw new IllegalStateException(); + } + + try { + this.tryMandatoryTransactional(null, false); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional + private void tryWithinTx() { + this.logger.info("Running inside read/write TX test"); + + String txId = AlfrescoTransactionSupport.getTransactionId(); + boolean readonly = false; + + this.tryDefaultTransactional(txId, readonly); + this.tryReadOnlyTransactional(txId, readonly); + this.tryRetryOnlyTransactional(txId); + this.trySupportsTransactional(txId, readonly); + this.tryRequiresNewTransactional(txId, readonly); + this.tryRequiredTransactional(txId, readonly); + this.tryMandatoryTransactional(txId, readonly); + + try { + this.tryNoSupportsTransactional(); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException uoe) { + // suppress + } + + try { + this.tryNeverTransactional(txId); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional(readOnly = true) + private void tryWithinReadonlyTx() { + this.logger.info("Running inside read-only TX test"); + + String txId = AlfrescoTransactionSupport.getTransactionId(); + boolean readonly = true; + + this.tryDefaultTransactional(txId, readonly); + this.tryReadOnlyTransactional(txId, readonly); + this.tryRetryOnlyTransactional(txId); + this.trySupportsTransactional(txId, readonly); + this.tryRequiresNewTransactional(txId, readonly); + this.tryRequiredTransactional(txId, readonly); + + try { + this.tryNoSupportsTransactional(); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException uoe) { + // suppress + } + + try { + this.tryMandatoryTransactional(txId, readonly); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + + try { + this.tryNeverTransactional(txId); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional + private void tryDefaultTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) { + if (originReadonly) { + // changed from readonly to read/write; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } else { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + } + + @Transactional(readOnly = true) + private void tryReadOnlyTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a readonly transaction"); + if (originTxId != null) { + if (originReadonly) { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } else { + // changed from read/write to readonly; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } + } + } + + @Transactional(propagation = Propagation.SUPPORTS) + private void trySupportsTransactional(String originTxId, boolean originReadonly) { + if (originTxId == null) { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction"); + } else { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction"); + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + + @Transactional(propagation = Propagation.REQUIRED) + private void tryRequiredTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) { + if (originReadonly) { + // changed from readonly to read/write; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } else { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + private void tryRequiresNewTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } + + @Transactional(propagation = Propagation.MANDATORY) + private void tryMandatoryTransactional(String originTxId, boolean originReadonly) { + if (originTxId == null) { + throw new IllegalStateException(); + } else { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction"); + } + } + + @Transactional(propagation = Propagation.NOT_SUPPORTED) + private void tryNoSupportsTransactional() { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction"); + Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction"); + } + + @Transactional(propagation = Propagation.NEVER) + private void tryNeverTransactional(String originTxId) { + if (originTxId == null) { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction"); + } else { + throw new IllegalStateException(); + } + } + + @TransactionalRetryable + private void tryRetryOnlyTransactional(String originTxId) { + if (originTxId == null) { + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId() != null, "Expected a new transaction"); + } else { + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + +} diff --git a/src/test/java/com/inteligr8/alfresco/annotations/AsynchronousTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/AsynchronousTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/AsynchronousTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/AsynchronousTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/AuthorizableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/AuthorizableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/AuthorizableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/AuthorizableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/AuthorizedTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/AuthorizedTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/AuthorizedTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/AuthorizedTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfChildAssocIsPrimaryTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfChildAssocIsPrimaryTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfChildAssocIsPrimaryTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfChildAssocIsPrimaryTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectConstrainableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectConstrainableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectConstrainableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectConstrainableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeAspectTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNodeExistsTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeExistsTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNodeExistsTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeExistsTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeConstrainableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeConstrainableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeConstrainableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeConstrainableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNodeTypeTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/IfNotNullTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/IfNotNullTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/IfNotNullTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/IfNotNullTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/InvalidAuthorizableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/InvalidAuthorizableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/InvalidAuthorizableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/InvalidAuthorizableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/JobSynchronizedTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/JobSynchronizedTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/JobSynchronizedTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/JobSynchronizedTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/NoExistAuthorizableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/NoExistAuthorizableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/NoExistAuthorizableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/NoExistAuthorizableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/NullAuthorizableTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/NullAuthorizableTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/NullAuthorizableTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/NullAuthorizableTest.java diff --git a/src/test/java/com/inteligr8/alfresco/annotations/ThreadedTest.java b/core/src/test/java/com/inteligr8/alfresco/annotations/ThreadedTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/ThreadedTest.java rename to core/src/test/java/com/inteligr8/alfresco/annotations/ThreadedTest.java diff --git a/src/test/resources/alfresco/extension/debug-log4j.properties b/core/src/test/resources/alfresco/extension/debug-log4j.properties similarity index 100% rename from src/test/resources/alfresco/extension/debug-log4j.properties rename to core/src/test/resources/alfresco/extension/debug-log4j.properties diff --git a/src/test/resources/alfresco/extension/debug-log4j2.properties b/core/src/test/resources/alfresco/extension/debug-log4j2.properties similarity index 100% rename from src/test/resources/alfresco/extension/debug-log4j2.properties rename to core/src/test/resources/alfresco/extension/debug-log4j2.properties diff --git a/src/test/resources/alfresco/extension/disable-webscript-caching-context.xml b/core/src/test/resources/alfresco/extension/disable-webscript-caching-context.xml similarity index 100% rename from src/test/resources/alfresco/extension/disable-webscript-caching-context.xml rename to core/src/test/resources/alfresco/extension/disable-webscript-caching-context.xml diff --git a/jakarta/metadata.keystore b/jakarta/metadata.keystore new file mode 100644 index 0000000..2c2a1d9 Binary files /dev/null and b/jakarta/metadata.keystore differ diff --git a/jakarta/pom.xml b/jakarta/pom.xml new file mode 100644 index 0000000..6897c05 --- /dev/null +++ b/jakarta/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + + com.inteligr8.alfresco + annotations-platform-module + 1.0-SNAPSHOT + ../ + + + annotations-jakarta-platform-module + jar + + + 23.2.1 + 23.2.0.60 + + + + + ${project.groupId} + annotations-core-platform-module + ${project.version} + + + jakarta.transaction + jakarta.transaction-api + provided + + + + + + + io.repaint.maven + tiles-maven-plugin + 2.40 + true + + + + com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0) + + + + + + \ No newline at end of file diff --git a/jakarta/rad.ps1 b/jakarta/rad.ps1 new file mode 100644 index 0000000..61bcb2f --- /dev/null +++ b/jakarta/rad.ps1 @@ -0,0 +1,74 @@ + +function discoverArtifactId { + $script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate) +} + +function rebuild { + echo "Rebuilding project ..." + mvn process-classes +} + +function start_ { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-classes +} + +function start_log { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad "-Ddocker.showLogs" process-classes +} + +function stop_ { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*) + echo "Removing containers ..." + docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*) +} + +function tail_logs { + param ( + $container + ) + + discoverArtifactId + docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container}) +} + +function list { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +switch ($args[0]) { + "start" { + start_ + } + "start_log" { + start_log + } + "stop" { + stop_ + } + "restart" { + stop_ + start_ + } + "rebuild" { + rebuild + } + "tail" { + tail_logs $args[1] + } + "containers" { + list + } + default { + echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + } +} + +echo "Completed!" + diff --git a/jakarta/rad.sh b/jakarta/rad.sh new file mode 100644 index 0000000..3c7c2fa --- /dev/null +++ b/jakarta/rad.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +discoverArtifactId() { + ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'` +} + +rebuild() { + echo "Rebuilding project ..." + mvn process-test-classes +} + +start() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-test-classes +} + +start_log() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad -Ddocker.showLogs process-test-classes +} + +stop() { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*` + echo "Removing containers ..." + docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*` +} + +tail_logs() { + discoverArtifactId + docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1` +} + +list() { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +case "$1" in + start) + start + ;; + start_log) + start_log + ;; + stop) + stop + ;; + restart) + stop + start + ;; + rebuild) + rebuild + ;; + tail) + tail_logs $2 + ;; + containers) + list + ;; + *) + echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + exit 1 +esac + +echo "Completed!" + diff --git a/jakarta/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java new file mode 100644 index 0000000..19a8d61 --- /dev/null +++ b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java @@ -0,0 +1,23 @@ +package com.inteligr8.alfresco.annotations.aspect; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +import jakarta.transaction.Transactional; + +/** + * @see jakarta.transaction.Transactional + */ +@Aspect +public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { + + @Override + public String getJtaInterfaceName() { + return Transactional.class.getName(); + } + + @Pointcut("@annotation(jakarta.transaction.Transactional) && execution(* *(..))") + public void isJtaTransactionalAnnotated() { + } + +} diff --git a/src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java similarity index 100% rename from src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java rename to jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java diff --git a/src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java similarity index 100% rename from src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java rename to jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java diff --git a/src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java similarity index 52% rename from src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java rename to jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java index 4b14c6b..6a10ee1 100644 --- a/src/main/jakarta-tx/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java +++ b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java @@ -97,81 +97,12 @@ import jakarta.jms.Session; * @author brian@inteligr8.com */ @Component("async.mq") -public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable { - +public class MqAsyncService extends AbstractMqAsyncService { + 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, String>, Method> methodCache; - - private ThreadLocal isAsync = ThreadLocal.withInitial(new Supplier() { - @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 */ @@ -179,12 +110,8 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic protected void init() { if (!this.enabled) return; - - try { - this.hostname = InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException uhe) { - this.hostname = "unknown"; - } + + super.init(); ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); @@ -194,65 +121,16 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic 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() { + super.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 { @@ -429,43 +307,6 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic } } - private Method findMethod(Class clazz, String methodName) { - Pair, 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 args) throws AsyncProcessException { this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args); @@ -509,131 +350,5 @@ public class MqAsyncService extends AbstractLifecycleBean implements AsyncServic 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 argMap = (Map) this.om.convertValue(arg, Map.class); - - Map versionPropertiesMap = (Map) 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 argMap = (Map) 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 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 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 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 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(); - } - } } diff --git a/src/main/jakarta-tx/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java b/jakarta/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java similarity index 100% rename from src/main/jakarta-tx/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java rename to jakarta/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java diff --git a/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties b/jakarta/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties similarity index 100% rename from src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties rename to jakarta/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties diff --git a/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java b/jakarta/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java similarity index 100% rename from src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java rename to jakarta/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java diff --git a/javax/metadata.keystore b/javax/metadata.keystore new file mode 100644 index 0000000..2c2a1d9 Binary files /dev/null and b/javax/metadata.keystore differ diff --git a/javax/pom.xml b/javax/pom.xml new file mode 100644 index 0000000..a79b186 --- /dev/null +++ b/javax/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + + com.inteligr8.alfresco + annotations-platform-module + 1.0-SNAPSHOT + ../ + + + annotations-javax-platform-module + jar + + + 22.22 + + + + + ${project.groupId} + annotations-core-platform-module + ${project.version} + + + javax.transaction + javax.transaction-api + 1.3 + provided + + + + + + + io.repaint.maven + tiles-maven-plugin + 2.40 + true + + + + com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0) + + com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0) + + + + + + \ No newline at end of file diff --git a/javax/rad.ps1 b/javax/rad.ps1 new file mode 100644 index 0000000..61bcb2f --- /dev/null +++ b/javax/rad.ps1 @@ -0,0 +1,74 @@ + +function discoverArtifactId { + $script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate) +} + +function rebuild { + echo "Rebuilding project ..." + mvn process-classes +} + +function start_ { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-classes +} + +function start_log { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad "-Ddocker.showLogs" process-classes +} + +function stop_ { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*) + echo "Removing containers ..." + docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*) +} + +function tail_logs { + param ( + $container + ) + + discoverArtifactId + docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container}) +} + +function list { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +switch ($args[0]) { + "start" { + start_ + } + "start_log" { + start_log + } + "stop" { + stop_ + } + "restart" { + stop_ + start_ + } + "rebuild" { + rebuild + } + "tail" { + tail_logs $args[1] + } + "containers" { + list + } + default { + echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + } +} + +echo "Completed!" + diff --git a/javax/rad.sh b/javax/rad.sh new file mode 100644 index 0000000..3c7c2fa --- /dev/null +++ b/javax/rad.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +discoverArtifactId() { + ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'` +} + +rebuild() { + echo "Rebuilding project ..." + mvn process-test-classes +} + +start() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-test-classes +} + +start_log() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad -Ddocker.showLogs process-test-classes +} + +stop() { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*` + echo "Removing containers ..." + docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*` +} + +tail_logs() { + discoverArtifactId + docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1` +} + +list() { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +case "$1" in + start) + start + ;; + start_log) + start_log + ;; + stop) + stop + ;; + restart) + stop + start + ;; + rebuild) + rebuild + ;; + tail) + tail_logs $2 + ;; + containers) + list + ;; + *) + echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + exit 1 +esac + +echo "Completed!" + diff --git a/javax/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java b/javax/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java new file mode 100644 index 0000000..2d655d1 --- /dev/null +++ b/javax/src/main/java/com/inteligr8/alfresco/annotations/aspect/RetryingTransactionAspect.java @@ -0,0 +1,21 @@ +package com.inteligr8.alfresco.annotations.aspect; + +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; + +/** + * @see javax.transaction.Transactional + */ +@Aspect +public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect { + + @Override + public String getJtaInterfaceName() { + return javax.transaction.Transactional.class.getName(); + } + + @Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))") + public void isJtaTransactionalAnnotated() { + } + +} diff --git a/src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java b/javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java similarity index 100% rename from src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java rename to javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransaction.java diff --git a/src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java b/javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java similarity index 100% rename from src/main/javax-tx/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java rename to javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/AlfrescoTransactionManager.java diff --git a/javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java b/javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java new file mode 100644 index 0000000..ce9b4b3 --- /dev/null +++ b/javax/src/main/java/com/inteligr8/alfresco/annotations/service/impl/MqAsyncService.java @@ -0,0 +1,296 @@ +package com.inteligr8.alfresco.annotations.service.impl; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Matcher; +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.activemq.ActiveMQConnectionFactory; +import org.apache.activemq.jms.pool.PooledConnectionFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.inteligr8.alfresco.annotations.AuthorizedAsSystem; +import com.inteligr8.alfresco.annotations.Threaded; +import com.inteligr8.alfresco.annotations.TransactionalRetryable; +import com.inteligr8.alfresco.annotations.service.AsyncProcessException; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; + +/** + * This class provides integration with MQ for the asynchronous method executions. + * + * @author brian@inteligr8.com + */ +@Component("async.mq") +public class MqAsyncService extends AbstractMqAsyncService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private PooledConnectionFactory factory; + + /** + * @PostConstruct does not work in ACS + */ + @PostConstruct + protected void init() { + if (!this.enabled) + return; + + super.init(); + + ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url); + + PooledConnectionFactory pool = new PooledConnectionFactory(); + pool.setConnectionFactory(factory); + pool.setMaxConnections(this.maxConnections); + pool.start(); + + this.factory = pool; + } + + @PreDestroy + protected void uninit() { + super.uninit(); + + if (this.factory != null) + this.factory.stop(); + } + + @Override + @Transactional + public void poll() throws AsyncProcessException { + this.logger.trace("poll()"); + this.isAsync.set(true); + + try { + Connection mqcon = this.factory.createConnection(this.username, this.password); + try { + mqcon.setClientID(this.clientId + "-service-" + this.hostname); + + Session mqsession = mqcon.createSession(true, Session.CLIENT_ACKNOWLEDGE); + try { + this.logger.debug("Polling messages for asynchronous policy execution"); + this.pollErrors(mqsession); + this.pollMain(mqsession); + } finally { + mqsession.close(); + } + } finally { + mqcon.close(); + } + } catch (JMSException je) { + throw new AsyncProcessException("A JMS messaging issue occurred", je); + } + } + + private void pollErrors(Session mqsession) throws JMSException { + this.logger.debug("Polling previously errored messages"); + + Queue mqqueue = mqsession.createQueue(this.errorQueueName); + Set 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 msgIds) throws JMSException { + Message mqmsg = consumer.receive(); + + if (msgIds != null && !msgIds.add(mqmsg.getJMSMessageID())) { + this.logger.debug("Received a message again; assuming we have (re)tried all errored messages: {}", mqmsg.getJMSMessageID()); + return null; + } + + try { + if (this.processIncomingMessage(mqsession, mqmsg, msgIds != null)) { + mqmsg.acknowledge(); + return true; + } + } catch (RuntimeException | Error e) { + this.logger.error("An unexpected issue occurred", e); + } + + return false; + } + + private boolean processIncomingMessage(Session mqsession, Message mqmsg, boolean isErrorQueue) throws JMSException { + String msgId = mqmsg.getJMSMessageID(); + this.logger.debug("Received message: {}", msgId); + + String type = mqmsg.getJMSType(); + Matcher matcher = this.typePattern.matcher(type); + if (!matcher.find()) { + this.logger.warn("The queue has a message ('{}') with an unsupported JMS type: {}", msgId, type); + return false; + } + + try { + Class beanClass = Class.forName(matcher.group(2)); + this.logger.trace("Preparing to execute using bean type: {}", beanClass); + Object bean = this.getApplicationContext().getBean(beanClass); + this.logger.trace("Found qualifying bean: {}", bean); + + String methodName = matcher.group(3); + Method method = this.findMethod(beanClass, methodName); + this.logger.trace("Found qualifying method: {}", method); + Parameter[] params = method.getParameters(); + + Object[] args = new Object[params.length]; + + for (int a = 0; a < args.length; a++) { + Object arg = mqmsg.getObjectProperty("arg" + a); + if (arg == null) + continue; + + args[a] = this.unmarshal(params[a], arg); + } + + switch (method.getName()) { + case "onLoadDynamicModel": + args[1] = args[0]; + args[0] = this.loadModel((NodeRef) args[1]); + } + + method.invoke(bean, args); + } catch (ClassNotFoundException cnfe) { + this.logger.error("A bean could not be found; will try on next restart"); + this.logger.error("The bean '{}' could not be found: {}", matcher.group(2), cnfe.getMessage()); + if (isErrorQueue) + return false; + this.moveToErrorQueue(mqsession, mqmsg); + } catch (IOException ie) { + this.logger.warn("This should never happen: " + ie.getMessage()); + // return to queue and retry indefinitely + return false; + } catch (NoSuchMethodException nsme) { + this.logger.error("A bean enum argument could not be constructed; will try on next restart"); + this.logger.error("An argument could not be The bean '{}' could not be found: {}", matcher.group(2), nsme.getMessage()); + if (isErrorQueue) + return false; + this.moveToErrorQueue(mqsession, mqmsg); + } catch (IllegalAccessException iae) { + this.logger.error("A bean method was not accessible (public); will try on next restart"); + this.logger.warn("The bean '{}' method '{}' is not accessible: {}", matcher.group(2), matcher.group(3), iae.getMessage()); + if (isErrorQueue) + return false; + this.moveToErrorQueue(mqsession, mqmsg); + } catch (InstantiationException | InvocationTargetException ie) { + this.logger.error("A bean method execution failed; will try on next restart"); + this.logger.warn("The bean '{}' method '{}' execution failed: {}", matcher.group(2), matcher.group(3), ie.getMessage()); + if (isErrorQueue) + return false; + this.moveToErrorQueue(mqsession, mqmsg); + } + + return true; + } + + private void moveToErrorQueue(Session mqsession, Message mqmsg) throws JMSException { + Queue mqqueue = mqsession.createQueue(this.errorQueueName); + + MessageProducer producer = mqsession.createProducer(mqqueue); + try { + producer.send(mqmsg); + } finally { + producer.close(); + } + } + + @Transactional + public void push(Object callbackBean, String callbackMethod, List 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); + } + } + +} diff --git a/src/main/javax-tx/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java b/javax/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java similarity index 100% rename from src/main/javax-tx/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java rename to javax/src/main/java/com/inteligr8/alfresco/annotations/util/JtaTransactionalAnnotationAdapter.java diff --git a/javax/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties b/javax/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties new file mode 100644 index 0000000..f821eae --- /dev/null +++ b/javax/src/main/resources/alfresco/module/com_inteligr8_alfresco_annotations-platform-module/module.properties @@ -0,0 +1,9 @@ +module.id=${project.groupId}.${project.artifactId} +module.title=${project.name} +module.description=${project.description} +module.version=${project.version} + +module.repo.version.min=6.0 +#module.repo.version.max= + +module.depends.com.inteligr8.alfresco.aspectj-platform-module=1.0-* diff --git a/javax/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java b/javax/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java new file mode 100644 index 0000000..b17d98f --- /dev/null +++ b/javax/src/test/java/com/inteligr8/alfresco/annotations/TransactionalTest.java @@ -0,0 +1,224 @@ +package com.inteligr8.alfresco.annotations; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.stereotype.Component; +import org.springframework.transaction.IllegalTransactionStateException; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +@Component +public class TransactionalTest extends AbstractLifecycleBean { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + protected void onBootstrap(ApplicationEvent event) { + this.logger.info("Running test: " + this.getClass()); + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "An unexpected transaction: " + AlfrescoTransactionSupport.getTransactionId()); + + this.tryOutsideTx(); + this.tryWithinTx(); + this.tryWithinReadonlyTx(); + } + + @Override + protected void onShutdown(ApplicationEvent event) { + } + + private void tryOutsideTx() { + this.logger.info("Running outside TX test"); + + this.tryDefaultTransactional(null, false); + this.tryReadOnlyTransactional(null, false); + this.tryRetryOnlyTransactional(null); + this.trySupportsTransactional(null, false); + this.tryRequiresNewTransactional(null, false); + this.tryRequiredTransactional(null, false); + this.tryNeverTransactional(null); + + try { + this.tryNoSupportsTransactional(); + } catch (IllegalTransactionStateException uoe) { + throw new IllegalStateException(); + } + + try { + this.tryMandatoryTransactional(null, false); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional + private void tryWithinTx() { + this.logger.info("Running inside read/write TX test"); + + String txId = AlfrescoTransactionSupport.getTransactionId(); + boolean readonly = false; + + this.tryDefaultTransactional(txId, readonly); + this.tryReadOnlyTransactional(txId, readonly); + this.tryRetryOnlyTransactional(txId); + this.trySupportsTransactional(txId, readonly); + this.tryRequiresNewTransactional(txId, readonly); + this.tryRequiredTransactional(txId, readonly); + this.tryMandatoryTransactional(txId, readonly); + + try { + this.tryNoSupportsTransactional(); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException uoe) { + // suppress + } + + try { + this.tryNeverTransactional(txId); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional(readOnly = true) + private void tryWithinReadonlyTx() { + this.logger.info("Running inside read-only TX test"); + + String txId = AlfrescoTransactionSupport.getTransactionId(); + boolean readonly = true; + + this.tryDefaultTransactional(txId, readonly); + this.tryReadOnlyTransactional(txId, readonly); + this.tryRetryOnlyTransactional(txId); + this.trySupportsTransactional(txId, readonly); + this.tryRequiresNewTransactional(txId, readonly); + this.tryRequiredTransactional(txId, readonly); + + try { + this.tryNoSupportsTransactional(); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException uoe) { + // suppress + } + + try { + this.tryMandatoryTransactional(txId, readonly); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + + try { + this.tryNeverTransactional(txId); + throw new IllegalStateException(); + } catch (IllegalTransactionStateException itse) { + // suppress + } + } + + @Transactional + private void tryDefaultTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) { + if (originReadonly) { + // changed from readonly to read/write; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } else { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + } + + @Transactional(readOnly = true) + private void tryReadOnlyTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a readonly transaction"); + if (originTxId != null) { + if (originReadonly) { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } else { + // changed from read/write to readonly; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } + } + } + + @Transactional(propagation = Propagation.SUPPORTS) + private void trySupportsTransactional(String originTxId, boolean originReadonly) { + if (originTxId == null) { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction"); + } else { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction"); + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + + @Transactional(propagation = Propagation.REQUIRED) + private void tryRequiredTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) { + if (originReadonly) { + // changed from readonly to read/write; need new TX + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } else { + // no changes; same TX + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + private void tryRequiresNewTransactional(String originTxId, boolean originReadonly) { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(TxnReadState.TXN_READ_WRITE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected a read/write transaction"); + if (originTxId != null) + Assert.isTrue(!AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected a different transaction: " + AlfrescoTransactionSupport.getTransactionId() + " == " + originTxId); + } + + @Transactional(propagation = Propagation.MANDATORY) + private void tryMandatoryTransactional(String originTxId, boolean originReadonly) { + if (originTxId == null) { + throw new IllegalStateException(); + } else { + Assert.hasText(AlfrescoTransactionSupport.getTransactionId(), "Expected a transaction"); + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + Assert.isTrue(originReadonly == TxnReadState.TXN_READ_ONLY.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected the same read-state transaction"); + } + } + + @Transactional(propagation = Propagation.NOT_SUPPORTED) + private void tryNoSupportsTransactional() { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction"); + Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction"); + } + + @Transactional(propagation = Propagation.NEVER) + private void tryNeverTransactional(String originTxId) { + if (originTxId == null) { + Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Unexpected transaction"); + } else { + throw new IllegalStateException(); + } + } + + @TransactionalRetryable + private void tryRetryOnlyTransactional(String originTxId) { + if (originTxId == null) { + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId() != null, "Expected a new transaction"); + } else { + Assert.isTrue(AlfrescoTransactionSupport.getTransactionId().equals(originTxId), "Expected the same transaction: " + AlfrescoTransactionSupport.getTransactionId() + " != " + originTxId); + } + } + +} diff --git a/pom.xml b/pom.xml index ec3a009..9290136 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.inteligr8.alfresco annotations-platform-module 1.0-SNAPSHOT - jar + pom Annotations ACS Platform Module A module to support annotation-based development for Alfresco Content Services modules. @@ -43,6 +43,8 @@ 8 4.8.0 + 7.4.1 + 22.22 1.9.19 -javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar @@ -82,49 +84,12 @@ test - - - - - org.codehaus.mojo - build-helper-maven-plugin - 3.6.0 - - - add-source - add-source - - - src/main/${source.tx.path} - - - - - - - maven-jar-plugin - - ${classifier.tx.name} - - - - io.repaint.maven - tiles-maven-plugin - 2.33 - true - - - - com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0) - - com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0) - - com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0) - - - - - + + + core + javax + jakarta + @@ -133,43 +98,6 @@ true - - javax.transaction - - true - - - javax-tx - javax_tx - 7.4.2 - 22.22 - - - - javax.transaction - javax.transaction-api - 1.3 - provided - - - - - jakarta.transaction - - jakarta-tx - jakarta_tx - 23.2.1 - 23.2.0.60 - - - - jakarta.transaction - jakarta.transaction-api - 2.0.1 - provided - - - ossrh-release