Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85ec7aa307 |
15
core/pom.xml
15
core/pom.xml
@@ -1,15 +0,0 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.inteligr8.alfresco</groupId>
|
|
||||||
<artifactId>annotations-platform-module</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>annotations-core-platform-module</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
</project>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Repeatable;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This annotation tells the framework to only execute the annotated method if
|
|
||||||
* the ACS authenticated user is authorized.
|
|
||||||
*
|
|
||||||
* @see com.inteligr8.alfresco.annotations.Authorizable
|
|
||||||
*/
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Repeatable(IfAuthorizeds.class)
|
|
||||||
public @interface IfAuthorized {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The authorities (users and/or user groups) to constrain for the
|
|
||||||
* authorization context. Only one authority needs to match. To
|
|
||||||
* require multiple authorities, use multiple @IfAuthorized
|
|
||||||
* annotations.
|
|
||||||
*
|
|
||||||
* @return An array of ACS authorities; empty means any authenticated user.
|
|
||||||
*/
|
|
||||||
String[] value() default "";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
public @interface IfAuthorizeds {
|
|
||||||
|
|
||||||
IfAuthorized[] value();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.aspect;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
||||||
import org.alfresco.util.collections.CollectionUtils;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
|
|
||||||
import org.springframework.extensions.webscripts.WebScriptException;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import com.inteligr8.alfresco.annotations.IfAuthorized;
|
|
||||||
import com.inteligr8.alfresco.annotations.context.WebScriptContext;
|
|
||||||
|
|
||||||
import net.sf.acegisecurity.GrantedAuthority;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This aspect implements the IfAuthorized annotation.
|
|
||||||
*
|
|
||||||
* @see com.inteligr8.alfresco.annotations.IfAuthorized
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
public class IfAuthorizedAspect {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private WebScriptContext wscontext;
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
private WebApplicationContext wacontext;
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.IfAuthorized) && execution(* *(..))")
|
|
||||||
public void isIfAuthorizedAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around("isIfAuthorizedAnnotated()")
|
|
||||||
public Object ifAuthorized(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
||||||
this.logger.trace("ifAuthorized({})", joinPoint);
|
|
||||||
|
|
||||||
if (this.wscontext != null && !RequiredAuthentication.user.equals(this.wscontext.getWebscript().getDescription().getRequiredAuthentication()))
|
|
||||||
return joinPoint.proceed();
|
|
||||||
|
|
||||||
if (!(joinPoint.getSignature() instanceof MethodSignature))
|
|
||||||
throw new IllegalStateException("The @IfAuthorized annotation must be on methods and methods have signatures");
|
|
||||||
|
|
||||||
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
|
|
||||||
Method method = methodSig.getMethod();
|
|
||||||
IfAuthorized[] ifauths = method.getAnnotationsByType(IfAuthorized.class);
|
|
||||||
for (IfAuthorized ifauth : ifauths) {
|
|
||||||
if (ifauth.value() != null && ifauth.value().length > 0) {
|
|
||||||
if (!this.isAuthorized(ifauth.value()))
|
|
||||||
return this.unauthorized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return joinPoint.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAuthorized(String[] permittedAuthorities) {
|
|
||||||
Set<String> permittedAuthoritiesSet = CollectionUtils.asSet(permittedAuthorities);
|
|
||||||
|
|
||||||
GrantedAuthority[] authenticatedAuthorities = AuthenticationUtil.getFullAuthentication().getAuthorities();
|
|
||||||
for (GrantedAuthority auth : authenticatedAuthorities) {
|
|
||||||
if (permittedAuthoritiesSet.contains(auth.getAuthority()))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Object unauthorized() {
|
|
||||||
if (this.wscontext != null) {
|
|
||||||
throw new WebScriptException(HttpStatus.FORBIDDEN.value(), "The authenticated user is not authorized to use this resource");
|
|
||||||
} else if (this.wacontext != null) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "The authenticated user is not authorized to use this resource");
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.aspect;
|
|
||||||
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.extensions.webscripts.WebScript;
|
|
||||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
|
||||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
|
||||||
import org.springframework.web.context.WebApplicationContext;
|
|
||||||
|
|
||||||
import com.inteligr8.alfresco.annotations.context.WebScriptContext;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This aspect captures the WebScript execution context.
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
public class WebScriptAspect {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
|
|
||||||
private ThreadLocal<WebScriptContext> context = new ThreadLocal<>();
|
|
||||||
|
|
||||||
@Pointcut("execution(public void org.springframework.extensions.webscripts.WebScript.execute(..))")
|
|
||||||
public void isWebScriptExecute() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around("isWebScriptExecute()")
|
|
||||||
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
||||||
this.logger.trace("execute({})", joinPoint);
|
|
||||||
|
|
||||||
WebScript ws = (WebScript) joinPoint.getTarget();
|
|
||||||
WebScriptRequest req = (WebScriptRequest) joinPoint.getArgs()[0];
|
|
||||||
WebScriptResponse res = (WebScriptResponse) joinPoint.getArgs()[1];
|
|
||||||
this.context.set(new WebScriptContext(ws, req, res));
|
|
||||||
|
|
||||||
return joinPoint.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope(WebApplicationContext.SCOPE_REQUEST)
|
|
||||||
public WebScriptContext getContext() {
|
|
||||||
return this.context.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.context;
|
|
||||||
|
|
||||||
import org.springframework.extensions.webscripts.WebScript;
|
|
||||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
|
||||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
|
||||||
|
|
||||||
public class WebScriptContext {
|
|
||||||
|
|
||||||
private final WebScript webscript;
|
|
||||||
private final WebScriptRequest request;
|
|
||||||
private final WebScriptResponse response;
|
|
||||||
|
|
||||||
public WebScriptContext(WebScript webscript, WebScriptRequest request, WebScriptResponse response) {
|
|
||||||
this.webscript = webscript;
|
|
||||||
this.request = request;
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebScript getWebscript() {
|
|
||||||
return webscript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebScriptRequest getRequest() {
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebScriptResponse getResponse() {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.service.impl;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.OffsetTime;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.temporal.Temporal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
|
||||||
import org.alfresco.model.ContentModel;
|
|
||||||
import org.alfresco.repo.cache.SimpleCache;
|
|
||||||
import org.alfresco.repo.dictionary.M2Model;
|
|
||||||
import org.alfresco.repo.version.common.VersionImpl;
|
|
||||||
import org.alfresco.service.cmr.action.Action;
|
|
||||||
import org.alfresco.service.cmr.action.ActionService;
|
|
||||||
import org.alfresco.service.cmr.dictionary.CustomModelService;
|
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
|
||||||
import org.alfresco.service.cmr.repository.ContentService;
|
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
|
||||||
import org.alfresco.service.cmr.version.Version;
|
|
||||||
import org.alfresco.service.namespace.NamespaceService;
|
|
||||||
import org.alfresco.service.namespace.QName;
|
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
|
||||||
import org.alfresco.util.Pair;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
|
||||||
import org.quartz.JobKey;
|
|
||||||
import org.quartz.Scheduler;
|
|
||||||
import org.quartz.SchedulerException;
|
|
||||||
import org.quartz.Trigger;
|
|
||||||
import org.quartz.TriggerBuilder;
|
|
||||||
import org.quartz.impl.JobDetailImpl;
|
|
||||||
import org.quartz.impl.StdSchedulerFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ApplicationEvent;
|
|
||||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.inteligr8.alfresco.annotations.Threadable;
|
|
||||||
import com.inteligr8.alfresco.annotations.job.AsyncJob;
|
|
||||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
|
||||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.PreDestroy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author brian@inteligr8.com
|
|
||||||
*/
|
|
||||||
public abstract class AbstractMqAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean, DisposableBean, Threadable {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations");
|
|
||||||
protected final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)");
|
|
||||||
protected final ObjectMapper om = new ObjectMapper();
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.enabled}")
|
|
||||||
protected boolean enabled;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.workerThreads}")
|
|
||||||
protected int workerThreads;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.url}")
|
|
||||||
protected String url;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.username}")
|
|
||||||
protected String username;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.password}")
|
|
||||||
protected String password;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.queue}")
|
|
||||||
protected String queueName;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.errorQueue}")
|
|
||||||
protected String errorQueueName;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.clientId}")
|
|
||||||
protected String clientId;
|
|
||||||
|
|
||||||
@Value("${inteligr8.async.mq.pool.max}")
|
|
||||||
protected short maxConnections;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected ActionService actionService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected ContentService contentService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected CustomModelService modelService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected DictionaryService dictionaryService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected NamespaceService namespaceService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected TransactionService txService;
|
|
||||||
|
|
||||||
protected String hostname;
|
|
||||||
|
|
||||||
protected SimpleCache<Pair<Class<?>, String>, Method> methodCache;
|
|
||||||
|
|
||||||
protected ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean get() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void afterPropertiesSet() throws Exception {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void destroy() throws Exception {
|
|
||||||
this.uninit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @PostConstruct does not work in ACS
|
|
||||||
*/
|
|
||||||
@PostConstruct
|
|
||||||
protected void init() {
|
|
||||||
if (!this.enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.hostname = InetAddress.getLocalHost().getHostName();
|
|
||||||
} catch (UnknownHostException uhe) {
|
|
||||||
this.hostname = "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.workerThreads <= 0)
|
|
||||||
throw new AlfrescoRuntimeException("The 'inteligr8.async.mq.workerThreads' property must be positive");
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
protected void uninit() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBootstrap(ApplicationEvent event) {
|
|
||||||
if (!this.enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
|
||||||
jobDetail.setKey(this.jobKey);
|
|
||||||
jobDetail.setRequestsRecovery(true);
|
|
||||||
jobDetail.setJobClass(AsyncJob.class);
|
|
||||||
jobDetail.getJobDataMap().put("asyncService", this);
|
|
||||||
|
|
||||||
Trigger trigger = TriggerBuilder.newTrigger()
|
|
||||||
.startNow()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
|
||||||
StdSchedulerFactory.getDefaultScheduler()
|
|
||||||
.scheduleJob(jobDetail, trigger);
|
|
||||||
} catch (SchedulerException se) {
|
|
||||||
this.logger.error("The MQ async service job failed to start; no asynchronous executions will be processed!", se);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onShutdown(ApplicationEvent event) {
|
|
||||||
try {
|
|
||||||
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
|
|
||||||
scheduler.deleteJob(this.jobKey);
|
|
||||||
} catch (SchedulerException se) {
|
|
||||||
this.logger.warn("The MQ async service job failed to stop", se);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return this.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer getThreads() {
|
|
||||||
return this.workerThreads;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCurrentThreadAsynchronous() {
|
|
||||||
return this.isAsync.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Method findMethod(Class<?> clazz, String methodName) {
|
|
||||||
Pair<Class<?>, String> key = new Pair<>(clazz, methodName);
|
|
||||||
Method method = this.methodCache.get(key);
|
|
||||||
if (method != null) {
|
|
||||||
this.logger.trace("Found method in cache: {}", method);
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace("Looping through bean type methods to find: {}", methodName);
|
|
||||||
|
|
||||||
for (Method amethod : clazz.getDeclaredMethods()) {
|
|
||||||
if (amethod.getName().equals(methodName)) {
|
|
||||||
this.logger.debug("Found and caching method: {} => {}", key, amethod);
|
|
||||||
this.methodCache.put(key, amethod);
|
|
||||||
return amethod;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("The bean (" + clazz + ") does not implement the method: " + methodName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException {
|
|
||||||
this.logger.trace("push({})", joinPoint);
|
|
||||||
|
|
||||||
if (!(joinPoint.getSignature() instanceof MethodSignature))
|
|
||||||
throw new IllegalStateException("The join point must be on methods and methods have signatures");
|
|
||||||
|
|
||||||
Object bean = joinPoint.getThis();
|
|
||||||
this.logger.debug("Queuing for bean: {}", bean.getClass());
|
|
||||||
|
|
||||||
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
|
|
||||||
Method method = methodSig.getMethod();
|
|
||||||
this.logger.debug("Queuing for method: {}", method);
|
|
||||||
|
|
||||||
this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException;
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked" })
|
|
||||||
protected Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
|
||||||
Class<?> paramType = param.getType();
|
|
||||||
this.logger.trace("Unmarshaling parameter of type: {}", paramType);
|
|
||||||
|
|
||||||
if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
|
|
||||||
this.logger.trace("Unmarshaling primitive: {}", arg);
|
|
||||||
return arg;
|
|
||||||
} else if (Temporal.class.isAssignableFrom(paramType)) {
|
|
||||||
if (OffsetDateTime.class.isAssignableFrom(paramType)) {
|
|
||||||
return OffsetDateTime.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(arg.toString()));
|
|
||||||
} else if (ZonedDateTime.class.isAssignableFrom(paramType)) {
|
|
||||||
return ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(arg.toString()));
|
|
||||||
} else if (LocalDate.class.isAssignableFrom(paramType)) {
|
|
||||||
return LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse(arg.toString()));
|
|
||||||
} else if (LocalDateTime.class.isAssignableFrom(paramType)) {
|
|
||||||
return LocalDateTime.from(DateTimeFormatter.ISO_LOCAL_DATE_TIME.parse(arg.toString()));
|
|
||||||
} else if (Instant.class.isAssignableFrom(paramType)) {
|
|
||||||
return Instant.from(DateTimeFormatter.ISO_INSTANT.parse(arg.toString()));
|
|
||||||
} else if (LocalTime.class.isAssignableFrom(paramType)) {
|
|
||||||
return LocalTime.from(DateTimeFormatter.ISO_LOCAL_TIME.parse(arg.toString()));
|
|
||||||
} else if (OffsetTime.class.isAssignableFrom(paramType)) {
|
|
||||||
return OffsetTime.from(DateTimeFormatter.ISO_OFFSET_TIME.parse(arg.toString()));
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
} else if (Version.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
|
||||||
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
|
|
||||||
|
|
||||||
Map<String, Serializable> versionPropertiesMap = (Map<String, Serializable>) argMap.get("properties");
|
|
||||||
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
|
|
||||||
|
|
||||||
Version version = new VersionImpl(versionPropertiesMap, nodeRef);
|
|
||||||
this.logger.trace("Unmarshaled version: {} = {}", param.getName(), version);
|
|
||||||
return version;
|
|
||||||
} else if (Action.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
|
||||||
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
|
|
||||||
|
|
||||||
String actionId = (String) argMap.get("actionId");
|
|
||||||
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
|
|
||||||
this.logger.trace("Unmarshaling action: {}, {}", actionId, nodeRef);
|
|
||||||
|
|
||||||
Action action = this.actionService.getAction(nodeRef, actionId);
|
|
||||||
this.logger.trace("Unmarshaled action: {} = {}", param.getName(), action);
|
|
||||||
return action;
|
|
||||||
} else if (Collection.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as JSON array: {}", arg);
|
|
||||||
return this.om.convertValue(arg, Collection.class);
|
|
||||||
} else if (Map.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
|
||||||
return this.om.convertValue(arg, Map.class);
|
|
||||||
} else if (QName.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as QName: {}", arg);
|
|
||||||
return QName.createQName((String) arg);
|
|
||||||
} else if (Enum.class.isAssignableFrom(paramType)) {
|
|
||||||
this.logger.trace("Unmarshaling as Enum: {}", arg);
|
|
||||||
Method cons = paramType.getDeclaredMethod("valueOf", String.class);
|
|
||||||
return cons.invoke(null, arg.toString());
|
|
||||||
} else {
|
|
||||||
this.logger.trace("Unmarshaling as POJO: {}", arg);
|
|
||||||
try {
|
|
||||||
Constructor<?> cons = paramType.getConstructor(String.class);
|
|
||||||
return cons.newInstance(arg.toString());
|
|
||||||
} catch (NoSuchMethodException nsme) {
|
|
||||||
Method method = paramType.getDeclaredMethod("valueOf", String.class);
|
|
||||||
return method.invoke(null, arg.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Object marshal(Object arg) {
|
|
||||||
if (arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
|
|
||||||
return arg;
|
|
||||||
} else if (arg instanceof Temporal) {
|
|
||||||
return arg.toString();
|
|
||||||
} else if (arg instanceof Version) {
|
|
||||||
Version version = (Version) arg;
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("nodeRef", version.getFrozenStateNodeRef());
|
|
||||||
map.put("properties", version.getVersionProperties());
|
|
||||||
|
|
||||||
this.logger.trace("Marshaling Version as JSON object: {}", map);
|
|
||||||
return this.om.convertValue(map, String.class);
|
|
||||||
} else if (arg instanceof Action) {
|
|
||||||
Action action = (Action) arg;
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
|
||||||
map.put("nodeRef", action.getNodeRef());
|
|
||||||
map.put("actionId", action.getId());
|
|
||||||
|
|
||||||
this.logger.trace("Marshaling Action as JSON object: {}", map);
|
|
||||||
return this.om.convertValue(map, String.class);
|
|
||||||
} else if (arg instanceof Collection<?>) {
|
|
||||||
List<Object> list = new ArrayList<>(((Collection<?>)arg).size());
|
|
||||||
for (Object obj : (Collection<?>) arg)
|
|
||||||
list.add(this.marshal(obj));
|
|
||||||
|
|
||||||
this.logger.trace("Marshaling Java Collection as JSON array: {}", list);
|
|
||||||
return this.om.convertValue(list, String.class);
|
|
||||||
} else if (arg instanceof Map<?, ?>) {
|
|
||||||
Map<Object, Object> map = new HashMap<>();
|
|
||||||
for (Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) {
|
|
||||||
Object key = this.marshal(entry.getKey());
|
|
||||||
Object value = this.marshal(entry.getValue());
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace("Marshaling Java Map as JSON object: {}", map);
|
|
||||||
return this.om.convertValue(map, String.class);
|
|
||||||
} else {
|
|
||||||
this.logger.trace("Marshaling Java object as JSON object: {}", arg);
|
|
||||||
return this.om.convertValue(arg, String.class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected M2Model loadModel(NodeRef nodeRef) throws IOException {
|
|
||||||
ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
|
||||||
InputStream istream = creader.getContentInputStream();
|
|
||||||
try {
|
|
||||||
return M2Model.createModel(istream);
|
|
||||||
} finally {
|
|
||||||
istream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
|
|
||||||
public interface JtaTransactionalAnnotationAdapter extends TransactionalAnnotationAdapter {
|
|
||||||
|
|
||||||
default boolean isReadOnly() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Propagation getPropagation();
|
|
||||||
|
|
||||||
default Isolation getIsolation() {
|
|
||||||
return Isolation.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
default int getTimeoutInSeconds() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Class<? extends Throwable>[] getRollbackFor();
|
|
||||||
|
|
||||||
Class<? extends Throwable>[] getNoRollbackFor();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.alfresco.util.Pair;
|
|
||||||
|
|
||||||
public class MapUtils {
|
|
||||||
|
|
||||||
public static <K, V> Map<K, V> build(Pair<K, V>... pairs) {
|
|
||||||
Map<K, V> map = new HashMap<>();
|
|
||||||
for (Pair<K, V> pair : pairs) {
|
|
||||||
map.put(pair.getFirst(), pair.getSecond());
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, String> build(String... keyValuePairs) {
|
|
||||||
if (keyValuePairs.length % 2 == 1)
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
|
|
||||||
Map<String, String> map = new HashMap<>();
|
|
||||||
for (int pair = 0; pair < keyValuePairs.length / 2; pair++) {
|
|
||||||
int base = pair * 2;
|
|
||||||
map.put(keyValuePairs[base], keyValuePairs[base + 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
|
||||||
public class SpringTransactionalAnnotationAdapter implements TransactionalAnnotationAdapter {
|
|
||||||
|
|
||||||
private final Transactional txl;
|
|
||||||
|
|
||||||
public SpringTransactionalAnnotationAdapter(Transactional txl) {
|
|
||||||
this.txl = txl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReadOnly() {
|
|
||||||
return this.txl.readOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Propagation getPropagation() {
|
|
||||||
return this.txl.propagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Isolation getIsolation() {
|
|
||||||
return this.txl.isolation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTimeoutInSeconds() {
|
|
||||||
return this.txl.timeout();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getRollbackFor() {
|
|
||||||
return this.txl.rollbackFor();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getNoRollbackFor() {
|
|
||||||
return this.txl.noRollbackFor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
|
|
||||||
public interface TransactionalAnnotationAdapter {
|
|
||||||
|
|
||||||
boolean isReadOnly();
|
|
||||||
|
|
||||||
Propagation getPropagation();
|
|
||||||
|
|
||||||
Isolation getIsolation();
|
|
||||||
|
|
||||||
int getTimeoutInSeconds();
|
|
||||||
|
|
||||||
Class<? extends Throwable>[] getRollbackFor();
|
|
||||||
|
|
||||||
Class<? extends Throwable>[] getNoRollbackFor();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.inteligr8.alfresco</groupId>
|
|
||||||
<artifactId>annotations-platform-module</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>annotations-jakarta-platform-module</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<alfresco.platform.version>23.2.1</alfresco.platform.version>
|
|
||||||
<alfresco.platform.war.version>23.2.0.60</alfresco.platform.war.version>
|
|
||||||
<tomcat-rad.version>10-2.1</tomcat-rad.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>annotations-core-platform-module</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.transaction</groupId>
|
|
||||||
<artifactId>jakarta.transaction-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>io.repaint.maven</groupId>
|
|
||||||
<artifactId>tiles-maven-plugin</artifactId>
|
|
||||||
<version>2.40</version>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<configuration>
|
|
||||||
<tiles>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
</tiles>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.aspect;
|
|
||||||
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.DeclarePrecedence;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see jakarta.transaction.Transactional
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
|
|
||||||
public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJtaInterfaceName() {
|
|
||||||
return Transactional.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))")
|
|
||||||
public void isTransactionalAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(jakarta.transaction.Transactional) && execution(* *(..))")
|
|
||||||
public void isJtaTransactionalAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))")
|
|
||||||
public void isTransactionalRetryableAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()")
|
|
||||||
public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
||||||
return super.retryingTransactional(joinPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.service.impl;
|
|
||||||
|
|
||||||
import javax.transaction.xa.XAResource;
|
|
||||||
|
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
|
||||||
import org.alfresco.repo.transaction.TransactionListener;
|
|
||||||
|
|
||||||
import jakarta.transaction.HeuristicMixedException;
|
|
||||||
import jakarta.transaction.HeuristicRollbackException;
|
|
||||||
import jakarta.transaction.NotSupportedException;
|
|
||||||
import jakarta.transaction.RollbackException;
|
|
||||||
import jakarta.transaction.Status;
|
|
||||||
import jakarta.transaction.Synchronization;
|
|
||||||
import jakarta.transaction.SystemException;
|
|
||||||
import jakarta.transaction.Transaction;
|
|
||||||
import jakarta.transaction.UserTransaction;
|
|
||||||
|
|
||||||
public class AlfrescoTransaction implements UserTransaction, Transaction {
|
|
||||||
|
|
||||||
private UserTransaction userTx;
|
|
||||||
|
|
||||||
public AlfrescoTransaction(UserTransaction userTx) {
|
|
||||||
this.userTx = userTx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void begin() throws NotSupportedException, SystemException {
|
|
||||||
this.userTx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
|
|
||||||
SecurityException, IllegalStateException, SystemException {
|
|
||||||
this.userTx.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean enlistResource(XAResource xaRes) throws RollbackException, IllegalStateException, SystemException {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStatus() throws SystemException {
|
|
||||||
return this.userTx.getStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerSynchronization(Synchronization sync)
|
|
||||||
throws RollbackException, IllegalStateException, SystemException {
|
|
||||||
AlfrescoTransactionSupport.bindListener(new TransactionListener() {
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeCompletion() {
|
|
||||||
sync.beforeCompletion();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeCommit(boolean readOnly) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterRollback() {
|
|
||||||
sync.afterCompletion(Status.STATUS_ROLLEDBACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterCommit() {
|
|
||||||
sync.afterCompletion(Status.STATUS_COMMITTED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rollback() throws IllegalStateException, SecurityException, SystemException {
|
|
||||||
this.userTx.rollback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRollbackOnly() throws IllegalStateException, SystemException {
|
|
||||||
this.userTx.setRollbackOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTransactionTimeout(int seconds) throws SystemException {
|
|
||||||
this.userTx.setTransactionTimeout(seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.service.impl;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import jakarta.transaction.HeuristicMixedException;
|
|
||||||
import jakarta.transaction.HeuristicRollbackException;
|
|
||||||
import jakarta.transaction.InvalidTransactionException;
|
|
||||||
import jakarta.transaction.NotSupportedException;
|
|
||||||
import jakarta.transaction.RollbackException;
|
|
||||||
import jakarta.transaction.Status;
|
|
||||||
import jakarta.transaction.SystemException;
|
|
||||||
import jakarta.transaction.Transaction;
|
|
||||||
import jakarta.transaction.TransactionManager;
|
|
||||||
import jakarta.transaction.UserTransaction;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This bean implements a standard TransactionManager for ACS.
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class AlfrescoTransactionManager implements TransactionManager {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TransactionService txService;
|
|
||||||
|
|
||||||
private ThreadLocal<AlfrescoTransaction> tx = ThreadLocal.withInitial(new Supplier<AlfrescoTransaction>() {
|
|
||||||
@Override
|
|
||||||
public AlfrescoTransaction get() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void begin() throws NotSupportedException, SystemException {
|
|
||||||
UserTransaction userTx = this.txService.getNonPropagatingUserTransaction();
|
|
||||||
AlfrescoTransaction tx = new AlfrescoTransaction(userTx);
|
|
||||||
tx.begin();
|
|
||||||
this.tx.set(tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException,
|
|
||||||
SecurityException, IllegalStateException, SystemException {
|
|
||||||
AlfrescoTransaction tx = this.tx.get();
|
|
||||||
if (tx == null)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
tx.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getStatus() throws SystemException {
|
|
||||||
AlfrescoTransaction tx = this.tx.get();
|
|
||||||
if (tx == null)
|
|
||||||
return Status.STATUS_NO_TRANSACTION;
|
|
||||||
|
|
||||||
return tx.getStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Transaction getTransaction() throws SystemException {
|
|
||||||
return this.tx.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void resume(Transaction tx) throws InvalidTransactionException, IllegalStateException, SystemException {
|
|
||||||
if (!(tx instanceof AlfrescoTransaction))
|
|
||||||
throw new InvalidTransactionException("An AlfrescoTransaction is expected; received: " + tx.getClass());
|
|
||||||
if (this.tx.get() != null)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
this.tx.set((AlfrescoTransaction) tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rollback() throws IllegalStateException, SecurityException, SystemException {
|
|
||||||
AlfrescoTransaction tx = this.tx.get();
|
|
||||||
if (tx == null)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
tx.rollback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRollbackOnly() throws IllegalStateException, SystemException {
|
|
||||||
AlfrescoTransaction tx = this.tx.get();
|
|
||||||
if (tx == null)
|
|
||||||
throw new IllegalStateException();
|
|
||||||
|
|
||||||
tx.setRollbackOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTransactionTimeout(int seconds) throws SystemException {
|
|
||||||
AlfrescoTransaction tx = this.tx.get();
|
|
||||||
if (tx != null)
|
|
||||||
tx.setTransactionTimeout(seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Transaction suspend() throws SystemException {
|
|
||||||
try {
|
|
||||||
return this.tx.get();
|
|
||||||
} finally {
|
|
||||||
this.tx.set(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
|
||||||
|
|
||||||
public class JakartaTransactionalAnnotationAdapter implements JtaTransactionalAnnotationAdapter {
|
|
||||||
|
|
||||||
public static final String JTA_INTERFACE_NAME = "jakarta.transaction.Transactional";
|
|
||||||
|
|
||||||
private final Transactional txl;
|
|
||||||
|
|
||||||
public static JakartaTransactionalAnnotationAdapter cast(Object obj) {
|
|
||||||
return new JakartaTransactionalAnnotationAdapter((Transactional) obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JakartaTransactionalAnnotationAdapter(Transactional txl) {
|
|
||||||
this.txl = txl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReadOnly() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Propagation getPropagation() {
|
|
||||||
switch (this.txl.value()) {
|
|
||||||
case MANDATORY:
|
|
||||||
return Propagation.MANDATORY;
|
|
||||||
case REQUIRED:
|
|
||||||
return Propagation.REQUIRED;
|
|
||||||
case REQUIRES_NEW:
|
|
||||||
return Propagation.REQUIRES_NEW;
|
|
||||||
case SUPPORTS:
|
|
||||||
return Propagation.SUPPORTS;
|
|
||||||
case NOT_SUPPORTED:
|
|
||||||
return Propagation.NOT_SUPPORTED;
|
|
||||||
case NEVER:
|
|
||||||
return Propagation.NEVER;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("This should never happen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Isolation getIsolation() {
|
|
||||||
return Isolation.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTimeoutInSeconds() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getRollbackFor() {
|
|
||||||
return this.txl.rollbackOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getNoRollbackFor() {
|
|
||||||
return this.txl.dontRollbackOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<aspectj>
|
|
||||||
<aspects>
|
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" />
|
|
||||||
</aspects>
|
|
||||||
</aspectj>
|
|
||||||
Binary file not shown.
@@ -1,54 +0,0 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>com.inteligr8.alfresco</groupId>
|
|
||||||
<artifactId>annotations-platform-module</artifactId>
|
|
||||||
<version>1.0-SNAPSHOT</version>
|
|
||||||
<relativePath>../</relativePath>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<artifactId>annotations-javax-platform-module</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<alfresco.platform.war.version>22.22</alfresco.platform.war.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>${project.groupId}</groupId>
|
|
||||||
<artifactId>annotations-core-platform-module</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.transaction</groupId>
|
|
||||||
<artifactId>javax.transaction-api</artifactId>
|
|
||||||
<version>1.3</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>io.repaint.maven</groupId>
|
|
||||||
<artifactId>tiles-maven-plugin</artifactId>
|
|
||||||
<version>2.40</version>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<configuration>
|
|
||||||
<tiles>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile -->
|
|
||||||
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0)</tile>
|
|
||||||
</tiles>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
|
|
||||||
function discoverArtifactId {
|
|
||||||
$script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate)
|
|
||||||
}
|
|
||||||
|
|
||||||
function rebuild {
|
|
||||||
echo "Rebuilding project ..."
|
|
||||||
mvn process-classes
|
|
||||||
}
|
|
||||||
|
|
||||||
function start_ {
|
|
||||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
|
||||||
mvn -Drad process-classes
|
|
||||||
}
|
|
||||||
|
|
||||||
function start_log {
|
|
||||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
|
||||||
mvn -Drad "-Ddocker.showLogs" process-classes
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop_ {
|
|
||||||
discoverArtifactId
|
|
||||||
echo "Stopping Docker containers that supported rapid application development ..."
|
|
||||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
|
||||||
echo "Stopping containers ..."
|
|
||||||
docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*)
|
|
||||||
echo "Removing containers ..."
|
|
||||||
docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*)
|
|
||||||
}
|
|
||||||
|
|
||||||
function tail_logs {
|
|
||||||
param (
|
|
||||||
$container
|
|
||||||
)
|
|
||||||
|
|
||||||
discoverArtifactId
|
|
||||||
docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container})
|
|
||||||
}
|
|
||||||
|
|
||||||
function list {
|
|
||||||
discoverArtifactId
|
|
||||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($args[0]) {
|
|
||||||
"start" {
|
|
||||||
start_
|
|
||||||
}
|
|
||||||
"start_log" {
|
|
||||||
start_log
|
|
||||||
}
|
|
||||||
"stop" {
|
|
||||||
stop_
|
|
||||||
}
|
|
||||||
"restart" {
|
|
||||||
stop_
|
|
||||||
start_
|
|
||||||
}
|
|
||||||
"rebuild" {
|
|
||||||
rebuild
|
|
||||||
}
|
|
||||||
"tail" {
|
|
||||||
tail_logs $args[1]
|
|
||||||
}
|
|
||||||
"containers" {
|
|
||||||
list
|
|
||||||
}
|
|
||||||
default {
|
|
||||||
echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Completed!"
|
|
||||||
|
|
||||||
71
javax/rad.sh
71
javax/rad.sh
@@ -1,71 +0,0 @@
|
|||||||
#!/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!"
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.aspect;
|
|
||||||
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
|
||||||
import org.aspectj.lang.annotation.DeclarePrecedence;
|
|
||||||
import org.aspectj.lang.annotation.Pointcut;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see javax.transaction.Transactional
|
|
||||||
*/
|
|
||||||
@Aspect
|
|
||||||
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
|
|
||||||
public class RetryingTransactionAspect extends AbstractRetryingTransactionAspect {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getJtaInterfaceName() {
|
|
||||||
return javax.transaction.Transactional.class.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))")
|
|
||||||
public void isTransactionalAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(javax.transaction.Transactional) && execution(* *(..))")
|
|
||||||
public void isJtaTransactionalAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))")
|
|
||||||
public void isTransactionalRetryableAnnotated() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()")
|
|
||||||
public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
|
|
||||||
return super.retryingTransactional(joinPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.service.impl;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import javax.jms.Connection;
|
|
||||||
import javax.jms.JMSException;
|
|
||||||
import javax.jms.Message;
|
|
||||||
import javax.jms.MessageConsumer;
|
|
||||||
import javax.jms.MessageProducer;
|
|
||||||
import javax.jms.Queue;
|
|
||||||
import javax.jms.Session;
|
|
||||||
|
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
|
||||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
|
||||||
import org.apache.activemq.jms.pool.PooledConnectionFactory;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import com.inteligr8.alfresco.annotations.AuthorizedAsSystem;
|
|
||||||
import com.inteligr8.alfresco.annotations.Threaded;
|
|
||||||
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
|
|
||||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.PreDestroy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class provides integration with MQ for the asynchronous method executions.
|
|
||||||
*
|
|
||||||
* @author brian@inteligr8.com
|
|
||||||
*/
|
|
||||||
@Component("async.mq")
|
|
||||||
public class MqAsyncService extends AbstractMqAsyncService {
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
|
||||||
|
|
||||||
private PooledConnectionFactory factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @PostConstruct does not work in ACS
|
|
||||||
*/
|
|
||||||
@PostConstruct
|
|
||||||
protected void init() {
|
|
||||||
if (!this.enabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
super.init();
|
|
||||||
|
|
||||||
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url);
|
|
||||||
|
|
||||||
PooledConnectionFactory pool = new PooledConnectionFactory();
|
|
||||||
pool.setConnectionFactory(factory);
|
|
||||||
pool.setMaxConnections(this.maxConnections);
|
|
||||||
pool.start();
|
|
||||||
|
|
||||||
this.factory = pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreDestroy
|
|
||||||
protected void uninit() {
|
|
||||||
super.uninit();
|
|
||||||
|
|
||||||
if (this.factory != null)
|
|
||||||
this.factory.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void poll() throws AsyncProcessException {
|
|
||||||
this.logger.trace("poll()");
|
|
||||||
this.isAsync.set(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
Connection mqcon = this.factory.createConnection(this.username, this.password);
|
|
||||||
try {
|
|
||||||
mqcon.setClientID(this.clientId + "-service-" + this.hostname);
|
|
||||||
|
|
||||||
Session mqsession = mqcon.createSession(true, Session.CLIENT_ACKNOWLEDGE);
|
|
||||||
try {
|
|
||||||
this.logger.debug("Polling messages for asynchronous policy execution");
|
|
||||||
this.pollErrors(mqsession);
|
|
||||||
this.pollMain(mqsession);
|
|
||||||
} finally {
|
|
||||||
mqsession.close();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mqcon.close();
|
|
||||||
}
|
|
||||||
} catch (JMSException je) {
|
|
||||||
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pollErrors(Session mqsession) throws JMSException {
|
|
||||||
this.logger.debug("Polling previously errored messages");
|
|
||||||
|
|
||||||
Queue mqqueue = mqsession.createQueue(this.errorQueueName);
|
|
||||||
Set<String> msgIds = new HashSet<>();
|
|
||||||
int ackd = 0;
|
|
||||||
|
|
||||||
MessageConsumer consumer = mqsession.createConsumer(mqqueue);
|
|
||||||
try {
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
|
||||||
Boolean processed = this.pollTx(mqsession, consumer, msgIds);
|
|
||||||
if (processed == null) {
|
|
||||||
break;
|
|
||||||
} else if (processed.booleanValue()) {
|
|
||||||
ackd++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
consumer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Successfully processed {} of {} previously errored messages", ackd, msgIds.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pollMain(Session mqsession) throws JMSException {
|
|
||||||
this.logger.debug("Polling ongoing messages ...");
|
|
||||||
|
|
||||||
Queue mqqueue = mqsession.createQueue(this.queueName);
|
|
||||||
this.pollMainThreaded(mqsession, mqqueue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Threaded(name = "mq-poll", join = true)
|
|
||||||
private void pollMainThreaded(Session mqsession, Queue mqqueue) throws JMSException {
|
|
||||||
MessageConsumer consumer = mqsession.createConsumer(mqqueue);
|
|
||||||
try {
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
|
||||||
pollTx(mqsession, consumer, null);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
consumer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
|
||||||
@TransactionalRetryable(maxRetries = 3)
|
|
||||||
@AuthorizedAsSystem
|
|
||||||
private Boolean pollTx(Session mqsession, MessageConsumer consumer, Set<String> msgIds) throws JMSException {
|
|
||||||
Message mqmsg = consumer.receive();
|
|
||||||
|
|
||||||
if (msgIds != null && !msgIds.add(mqmsg.getJMSMessageID())) {
|
|
||||||
this.logger.debug("Received a message again; assuming we have (re)tried all errored messages: {}", mqmsg.getJMSMessageID());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.processIncomingMessage(mqsession, mqmsg, msgIds != null)) {
|
|
||||||
mqmsg.acknowledge();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (RuntimeException | Error e) {
|
|
||||||
this.logger.error("An unexpected issue occurred", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean processIncomingMessage(Session mqsession, Message mqmsg, boolean isErrorQueue) throws JMSException {
|
|
||||||
String msgId = mqmsg.getJMSMessageID();
|
|
||||||
this.logger.debug("Received message: {}", msgId);
|
|
||||||
|
|
||||||
String type = mqmsg.getJMSType();
|
|
||||||
Matcher matcher = this.typePattern.matcher(type);
|
|
||||||
if (!matcher.find()) {
|
|
||||||
this.logger.warn("The queue has a message ('{}') with an unsupported JMS type: {}", msgId, type);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Class<?> beanClass = Class.forName(matcher.group(2));
|
|
||||||
this.logger.trace("Preparing to execute using bean type: {}", beanClass);
|
|
||||||
Object bean = this.getApplicationContext().getBean(beanClass);
|
|
||||||
this.logger.trace("Found qualifying bean: {}", bean);
|
|
||||||
|
|
||||||
String methodName = matcher.group(3);
|
|
||||||
Method method = this.findMethod(beanClass, methodName);
|
|
||||||
this.logger.trace("Found qualifying method: {}", method);
|
|
||||||
Parameter[] params = method.getParameters();
|
|
||||||
|
|
||||||
Object[] args = new Object[params.length];
|
|
||||||
|
|
||||||
for (int a = 0; a < args.length; a++) {
|
|
||||||
Object arg = mqmsg.getObjectProperty("arg" + a);
|
|
||||||
if (arg == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
args[a] = this.unmarshal(params[a], arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (method.getName()) {
|
|
||||||
case "onLoadDynamicModel":
|
|
||||||
args[1] = args[0];
|
|
||||||
args[0] = this.loadModel((NodeRef) args[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
method.invoke(bean, args);
|
|
||||||
} catch (ClassNotFoundException cnfe) {
|
|
||||||
this.logger.error("A bean could not be found; will try on next restart");
|
|
||||||
this.logger.error("The bean '{}' could not be found: {}", matcher.group(2), cnfe.getMessage());
|
|
||||||
if (isErrorQueue)
|
|
||||||
return false;
|
|
||||||
this.moveToErrorQueue(mqsession, mqmsg);
|
|
||||||
} catch (IOException ie) {
|
|
||||||
this.logger.warn("This should never happen: " + ie.getMessage());
|
|
||||||
// return to queue and retry indefinitely
|
|
||||||
return false;
|
|
||||||
} catch (NoSuchMethodException nsme) {
|
|
||||||
this.logger.error("A bean enum argument could not be constructed; will try on next restart");
|
|
||||||
this.logger.error("An argument could not be The bean '{}' could not be found: {}", matcher.group(2), nsme.getMessage());
|
|
||||||
if (isErrorQueue)
|
|
||||||
return false;
|
|
||||||
this.moveToErrorQueue(mqsession, mqmsg);
|
|
||||||
} catch (IllegalAccessException iae) {
|
|
||||||
this.logger.error("A bean method was not accessible (public); will try on next restart");
|
|
||||||
this.logger.warn("The bean '{}' method '{}' is not accessible: {}", matcher.group(2), matcher.group(3), iae.getMessage());
|
|
||||||
if (isErrorQueue)
|
|
||||||
return false;
|
|
||||||
this.moveToErrorQueue(mqsession, mqmsg);
|
|
||||||
} catch (InstantiationException | InvocationTargetException ie) {
|
|
||||||
this.logger.error("A bean method execution failed; will try on next restart");
|
|
||||||
this.logger.warn("The bean '{}' method '{}' execution failed: {}", matcher.group(2), matcher.group(3), ie.getMessage());
|
|
||||||
if (isErrorQueue)
|
|
||||||
return false;
|
|
||||||
this.moveToErrorQueue(mqsession, mqmsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveToErrorQueue(Session mqsession, Message mqmsg) throws JMSException {
|
|
||||||
Queue mqqueue = mqsession.createQueue(this.errorQueueName);
|
|
||||||
|
|
||||||
MessageProducer producer = mqsession.createProducer(mqqueue);
|
|
||||||
try {
|
|
||||||
producer.send(mqmsg);
|
|
||||||
} finally {
|
|
||||||
producer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
|
||||||
public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException {
|
|
||||||
this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args);
|
|
||||||
|
|
||||||
UUID msgId = UUID.randomUUID();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Connection mqcon = this.factory.createConnection(this.username, this.password);
|
|
||||||
try {
|
|
||||||
mqcon.setClientID(this.clientId + "-client-" + this.hostname);
|
|
||||||
|
|
||||||
Session mqsession = mqcon.createSession(true, Session.AUTO_ACKNOWLEDGE);
|
|
||||||
try {
|
|
||||||
this.logger.trace("Sending policy as message: {} => {}", callbackMethod, msgId);
|
|
||||||
|
|
||||||
Queue mqqueue = mqsession.createQueue(this.queueName);
|
|
||||||
|
|
||||||
Message mqmsg = mqsession.createMessage();
|
|
||||||
mqmsg.setJMSMessageID(msgId.toString());
|
|
||||||
mqmsg.setJMSType("v1:" + callbackBean.getClass() + "#" + callbackMethod);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (Object arg : args)
|
|
||||||
mqmsg.setObjectProperty("arg" + (i++), this.marshal(arg));
|
|
||||||
|
|
||||||
MessageProducer producer = mqsession.createProducer(mqqueue);
|
|
||||||
try {
|
|
||||||
producer.send(mqmsg);
|
|
||||||
} finally {
|
|
||||||
producer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.debug("Sent node as message: {} => {}", callbackMethod, msgId);
|
|
||||||
} finally {
|
|
||||||
mqsession.close();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
mqcon.close();
|
|
||||||
}
|
|
||||||
} catch (JMSException je) {
|
|
||||||
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package com.inteligr8.alfresco.annotations.util;
|
|
||||||
|
|
||||||
import javax.transaction.Transactional;
|
|
||||||
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
|
||||||
|
|
||||||
public class JavaxTransactionalAnnotationAdapter implements JtaTransactionalAnnotationAdapter {
|
|
||||||
|
|
||||||
public static final String JTA_INTERFACE_NAME = "javax.transaction.Transactional";
|
|
||||||
|
|
||||||
private final Transactional txl;
|
|
||||||
|
|
||||||
public static JavaxTransactionalAnnotationAdapter cast(Object obj) {
|
|
||||||
return new JavaxTransactionalAnnotationAdapter((Transactional) obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JavaxTransactionalAnnotationAdapter(Transactional txl) {
|
|
||||||
this.txl = txl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReadOnly() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Propagation getPropagation() {
|
|
||||||
switch (this.txl.value()) {
|
|
||||||
case MANDATORY:
|
|
||||||
return Propagation.MANDATORY;
|
|
||||||
case REQUIRED:
|
|
||||||
return Propagation.REQUIRED;
|
|
||||||
case REQUIRES_NEW:
|
|
||||||
return Propagation.REQUIRES_NEW;
|
|
||||||
case SUPPORTS:
|
|
||||||
return Propagation.SUPPORTS;
|
|
||||||
case NOT_SUPPORTED:
|
|
||||||
return Propagation.NOT_SUPPORTED;
|
|
||||||
case NEVER:
|
|
||||||
return Propagation.NEVER;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("This should never happen");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Isolation getIsolation() {
|
|
||||||
return Isolation.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTimeoutInSeconds() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getRollbackFor() {
|
|
||||||
return this.txl.rollbackOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public Class<? extends Throwable>[] getNoRollbackFor() {
|
|
||||||
return this.txl.dontRollbackOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<aspectj>
|
|
||||||
<aspects>
|
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" />
|
|
||||||
</aspects>
|
|
||||||
</aspectj>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.id=${project.groupId}.${project.artifactId}
|
|
||||||
module.alias=${project.groupId}.annotations-platform-module
|
|
||||||
module.title=${project.name}
|
|
||||||
module.description=${project.description}
|
|
||||||
module.version=${module.version}
|
|
||||||
|
|
||||||
module.repo.version.min=6.0
|
|
||||||
#module.repo.version.max=
|
|
||||||
|
|
||||||
module.depends.com.inteligr8.alfresco.aspectj-platform-module=1.0-*
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
51
pom.xml
51
pom.xml
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
<groupId>com.inteligr8.alfresco</groupId>
|
<groupId>com.inteligr8.alfresco</groupId>
|
||||||
<artifactId>annotations-platform-module</artifactId>
|
<artifactId>annotations-platform-module</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0.0</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>Annotations ACS Platform Module</name>
|
<name>Annotations ACS Platform Module</name>
|
||||||
<description>A module to support annotation-based development for Alfresco Content Services modules.</description>
|
<description>A module to support annotation-based development for Alfresco Content Services modules.</description>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
|
||||||
<alfresco.sdk.version>4.8.0</alfresco.sdk.version>
|
<alfresco.sdk.version>4.8.0</alfresco.sdk.version>
|
||||||
<alfresco.platform.version>7.4.1</alfresco.platform.version>
|
<alfresco.platform.version>7.4.2</alfresco.platform.version>
|
||||||
<alfresco.platform.war.version>22.22</alfresco.platform.war.version>
|
<alfresco.platform.war.version>22.22</alfresco.platform.war.version>
|
||||||
<aspectj.version>1.9.19</aspectj.version>
|
<aspectj.version>1.9.19</aspectj.version>
|
||||||
<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts>
|
<acs-platform.tomcat.opts>-javaagent:/var/lib/tomcat/dev/lib/aspectjweaver-${aspectj.version}.jar</acs-platform.tomcat.opts>
|
||||||
@@ -72,32 +72,33 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.inteligr8.alfresco</groupId>
|
<groupId>com.inteligr8.alfresco</groupId>
|
||||||
<artifactId>aspectj-platform-module</artifactId>
|
<artifactId>aspectj-platform-module</artifactId>
|
||||||
<version>1.0.1</version>
|
<version>1.0.0</version>
|
||||||
<type>amp</type>
|
<scope>provided</scope>
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- AMP resources are included in the WAR, not the extension directory; this makes aspectjweaver available to javaagent -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.aspectj</groupId>
|
|
||||||
<artifactId>aspectjweaver</artifactId>
|
|
||||||
<version>${aspectj.version}</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<modules>
|
<build>
|
||||||
<module>core</module>
|
<plugins>
|
||||||
<module>javax</module>
|
<plugin>
|
||||||
<module>jakarta</module>
|
<groupId>io.repaint.maven</groupId>
|
||||||
</modules>
|
<artifactId>tiles-maven-plugin</artifactId>
|
||||||
|
<version>2.33</version>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<configuration>
|
||||||
|
<tiles>
|
||||||
|
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-rad-tile -->
|
||||||
|
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-rad-tile:[1.1.0,1.2.0)</tile>
|
||||||
|
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-module-tile -->
|
||||||
|
<tile>com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.0,1.2.0)</tile>
|
||||||
|
<!-- Documentation: https://bitbucket.org/inteligr8/ootbee-beedk/src/stable/beedk-acs-platform-self-it-tile -->
|
||||||
|
<tile>com.inteligr8.ootbee:beedk-acs-platform-self-it-tile:[1.1.0,1.2.0)</tile>
|
||||||
|
</tiles>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
<profile>
|
|
||||||
<id>debug</id>
|
|
||||||
<properties>
|
|
||||||
<docker.showLogs>true</docker.showLogs>
|
|
||||||
</properties>
|
|
||||||
</profile>
|
|
||||||
<profile>
|
<profile>
|
||||||
<id>ossrh-release</id>
|
<id>ossrh-release</id>
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import java.util.HashSet;
|
|||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
import org.alfresco.repo.cache.DefaultSimpleCache;
|
import org.alfresco.repo.cache.DefaultSimpleCache;
|
||||||
import org.alfresco.repo.cache.SimpleCache;
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
import org.alfresco.service.cmr.repository.AssociationRef;
|
import org.alfresco.service.cmr.repository.AssociationRef;
|
||||||
@@ -21,8 +23,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
|
|
||||||
public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> {
|
public abstract class QNameBasedAspect<T extends Annotation> extends AbstractMethodOrParameterAspect<T> {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.inteligr8.alfresco.annotations.aspect;
|
package com.inteligr8.alfresco.annotations.aspect;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||||
@@ -9,18 +8,18 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.Around;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.aspectj.lang.annotation.DeclarePrecedence;
|
||||||
|
import org.aspectj.lang.annotation.Pointcut;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.transaction.IllegalTransactionStateException;
|
import org.springframework.transaction.IllegalTransactionStateException;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
|
import com.inteligr8.alfresco.annotations.TransactionalRetryable;
|
||||||
import com.inteligr8.alfresco.annotations.util.JtaTransactionalAnnotationAdapter;
|
|
||||||
import com.inteligr8.alfresco.annotations.util.SpringTransactionalAnnotationAdapter;
|
|
||||||
import com.inteligr8.alfresco.annotations.util.TransactionalAnnotationAdapter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This aspect implements the @Transactional and @TransactionalRetryable
|
* This aspect implements the @Transactional and @TransactionalRetryable
|
||||||
@@ -38,45 +37,33 @@ import com.inteligr8.alfresco.annotations.util.TransactionalAnnotationAdapter;
|
|||||||
* @see org.springframework.transaction.annotation.Transactional
|
* @see org.springframework.transaction.annotation.Transactional
|
||||||
* @see com.inteligr8.alfresco.annotations.TransactionalRetryable
|
* @see com.inteligr8.alfresco.annotations.TransactionalRetryable
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractRetryingTransactionAspect {
|
@Aspect
|
||||||
|
@DeclarePrecedence("com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect, com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect")
|
||||||
|
public class RetryingTransactionAspect {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApplicationContext context;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TransactionService txService;
|
private TransactionService txService;
|
||||||
|
|
||||||
public abstract String getJtaInterfaceName();
|
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))")
|
||||||
|
public void isTransactionalAnnotated() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))")
|
||||||
* A @Pointcunt annotation is not recognized in super classes.
|
public void isTransactionalRetryableAnnotated() {
|
||||||
*/
|
}
|
||||||
//@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional) && execution(* *(..))")
|
|
||||||
public abstract void isTransactionalAnnotated();
|
|
||||||
|
|
||||||
public abstract void isJtaTransactionalAnnotated();
|
@Around("isTransactionalAnnotated() || isTransactionalRetryableAnnotated()")
|
||||||
|
|
||||||
/**
|
|
||||||
* A @Pointcunt annotation is not recognized in super classes.
|
|
||||||
*/
|
|
||||||
//@Pointcut("@annotation(com.inteligr8.alfresco.annotations.TransactionalRetryable) && execution(* *(..))")
|
|
||||||
public abstract void isTransactionalRetryableAnnotated();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An @Around annotation is not recognized in super classes.
|
|
||||||
*/
|
|
||||||
//@Around("isTransactionalAnnotated() || isJtaTransactionalAnnotated() || isTransactionalRetryableAnnotated()")
|
|
||||||
public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
|
public Object retryingTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
this.logger.trace("retryingTransactional({})", joinPoint);
|
this.logger.trace("retryingTransactional({})", joinPoint);
|
||||||
|
|
||||||
Method method = this.getMethod(joinPoint);
|
Method method = this.getMethod(joinPoint);
|
||||||
TransactionalAnnotationAdapter txl = this.wrapTransactionalAnnotation(method);
|
Transactional txl = method.getAnnotation(Transactional.class);
|
||||||
TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class);
|
TransactionalRetryable txtry = method.getAnnotation(TransactionalRetryable.class);
|
||||||
|
|
||||||
if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) {
|
if (this.doCreateNewTxContext(txl) || this.isReadStateChange(txl)) {
|
||||||
this.logger.debug("Changing TX context: {} => [ro: {}, new: {}]", AlfrescoTransactionSupport.getTransactionReadState(), txl.isReadOnly(), txl.getPropagation());
|
this.logger.debug("Changing TX context: {} => [ro: {}, new: {}]", AlfrescoTransactionSupport.getTransactionReadState(), txl.readOnly(), txl.propagation());
|
||||||
return this.execute(joinPoint, txl, txtry);
|
return this.execute(joinPoint, txl, txtry);
|
||||||
} else if (this.doCreateNewTxRetryContext(txtry)) {
|
} else if (this.doCreateNewTxRetryContext(txtry)) {
|
||||||
this.logger.debug("Changing TX context: retries: {}", txtry.maxRetries());
|
this.logger.debug("Changing TX context: retries: {}", txtry.maxRetries());
|
||||||
@@ -86,29 +73,6 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransactionalAnnotationAdapter wrapTransactionalAnnotation(Method method) {
|
|
||||||
Annotation txl = method.getAnnotation(Transactional.class);
|
|
||||||
if (txl != null)
|
|
||||||
return new SpringTransactionalAnnotationAdapter((Transactional) txl);
|
|
||||||
|
|
||||||
txl = this.getOptionalAnnotation(method, this.getJtaInterfaceName());
|
|
||||||
if (txl != null)
|
|
||||||
return this.context.getAutowireCapableBeanFactory().getBean(JtaTransactionalAnnotationAdapter.class, txl);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <A extends Annotation> A getOptionalAnnotation(Method method, String fullyQualifiedAnnotationName) {
|
|
||||||
try {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<A> annotationClass = (Class<A>) Class.forName(fullyQualifiedAnnotationName);
|
|
||||||
return method.getAnnotation(annotationClass);
|
|
||||||
} catch (ClassNotFoundException cnfe) {
|
|
||||||
this.logger.trace("The {} annotation is not available in the classpath; assuming not set", fullyQualifiedAnnotationName);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Method getMethod(ProceedingJoinPoint joinPoint) {
|
private Method getMethod(ProceedingJoinPoint joinPoint) {
|
||||||
if (!(joinPoint.getSignature() instanceof MethodSignature))
|
if (!(joinPoint.getSignature() instanceof MethodSignature))
|
||||||
throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods");
|
throw new IllegalStateException("The @Transactional or @TransactionalRetryable annotations must be on methods");
|
||||||
@@ -117,11 +81,11 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
return methodSig.getMethod();
|
return methodSig.getMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isReadStateChange(TransactionalAnnotationAdapter txl) {
|
private boolean isReadStateChange(Transactional txl) {
|
||||||
if (txl == null)
|
if (txl == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
switch (txl.getPropagation()) {
|
switch (txl.propagation()) {
|
||||||
case NEVER:
|
case NEVER:
|
||||||
case NOT_SUPPORTED:
|
case NOT_SUPPORTED:
|
||||||
case SUPPORTS:
|
case SUPPORTS:
|
||||||
@@ -134,9 +98,9 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
case TXN_NONE:
|
case TXN_NONE:
|
||||||
return true;
|
return true;
|
||||||
case TXN_READ_ONLY:
|
case TXN_READ_ONLY:
|
||||||
return !txl.isReadOnly();
|
return !txl.readOnly();
|
||||||
case TXN_READ_WRITE:
|
case TXN_READ_WRITE:
|
||||||
return txl.isReadOnly();
|
return txl.readOnly();
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
@@ -146,10 +110,10 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
return txtry != null;
|
return txtry != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean doCreateNewTxContext(TransactionalAnnotationAdapter txl) {
|
private boolean doCreateNewTxContext(Transactional txl) {
|
||||||
if (txl == null) {
|
if (txl == null) {
|
||||||
return false;
|
return false;
|
||||||
} else switch (txl.getPropagation()) {
|
} else switch (txl.propagation()) {
|
||||||
case NEVER:
|
case NEVER:
|
||||||
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
||||||
case TXN_NONE:
|
case TXN_NONE:
|
||||||
@@ -162,21 +126,14 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
case TXN_NONE:
|
case TXN_NONE:
|
||||||
throw new IllegalTransactionStateException("A transaction does not exist where one is mandatory");
|
throw new IllegalTransactionStateException("A transaction does not exist where one is mandatory");
|
||||||
case TXN_READ_ONLY:
|
case TXN_READ_ONLY:
|
||||||
if (!txl.isReadOnly())
|
if (!txl.readOnly())
|
||||||
throw new IllegalTransactionStateException("A read-only transaction exists where a read/write one is mandatory");
|
throw new IllegalTransactionStateException("A read-only transaction exists where a read/write one is mandatory");
|
||||||
case TXN_READ_WRITE:
|
case TXN_READ_WRITE:
|
||||||
if (txl.isReadOnly())
|
if (txl.readOnly())
|
||||||
throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory");
|
throw new IllegalTransactionStateException("A read/write transaction exists where a read-only one is mandatory");
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
case NOT_SUPPORTED:
|
|
||||||
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
|
||||||
case TXN_NONE:
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
throw new IllegalTransactionStateException("A transaction exists and pausing it is not supported");
|
|
||||||
}
|
|
||||||
case SUPPORTS:
|
case SUPPORTS:
|
||||||
|
//case NOT_SUPPORTED: not supported; we would have to create another thread to simulate
|
||||||
return false;
|
return false;
|
||||||
case REQUIRED:
|
case REQUIRED:
|
||||||
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
switch (AlfrescoTransactionSupport.getTransactionReadState()) {
|
||||||
@@ -188,11 +145,11 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
case REQUIRES_NEW:
|
case REQUIRES_NEW:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
throw new IllegalTransactionStateException("The transactional propagation is not supported: " + txl.getPropagation());
|
throw new IllegalTransactionStateException("The transactional propagation is not supported: " + txl.propagation());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object execute(final ProceedingJoinPoint joinPoint, TransactionalAnnotationAdapter txl, TransactionalRetryable txtry) throws Throwable {
|
private Object execute(final ProceedingJoinPoint joinPoint, Transactional txl, TransactionalRetryable txtry) throws Throwable {
|
||||||
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
|
RetryingTransactionCallback<Object> rtcallback = new RetryingTransactionCallback<Object>() {
|
||||||
@Override
|
@Override
|
||||||
public Object execute() throws Throwable {
|
public Object execute() throws Throwable {
|
||||||
@@ -222,12 +179,12 @@ public abstract class AbstractRetryingTransactionAspect {
|
|||||||
if (txtry.incRetryWaitInMillis() > 0)
|
if (txtry.incRetryWaitInMillis() > 0)
|
||||||
rthelper.setRetryWaitIncrementMs(txtry.incRetryWaitInMillis());
|
rthelper.setRetryWaitIncrementMs(txtry.incRetryWaitInMillis());
|
||||||
}
|
}
|
||||||
if (txl != null && txl.getTimeoutInSeconds() > 0)
|
if (txl != null && txl.timeout() > 0)
|
||||||
rthelper.setMaxExecutionMs(txl.getTimeoutInSeconds() * 1000L);
|
rthelper.setMaxExecutionMs(txl.timeout() * 1000L);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.logger.trace("source tx: {}", AlfrescoTransactionSupport.getTransactionId());
|
this.logger.trace("source tx: {}", AlfrescoTransactionSupport.getTransactionId());
|
||||||
boolean readonly = txl != null && txl.isReadOnly() || txl == null && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
|
boolean readonly = txl != null && txl.readOnly() || txl == null && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
|
||||||
return rthelper.doInTransaction(rtcallback, readonly, txl != null);
|
return rthelper.doInTransaction(rtcallback, readonly, txl != null);
|
||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException re) {
|
||||||
// attempt to unwrap the exception
|
// attempt to unwrap the exception
|
||||||
@@ -9,15 +9,6 @@ import java.lang.reflect.Method;
|
|||||||
import java.lang.reflect.Parameter;
|
import java.lang.reflect.Parameter;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.OffsetTime;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.temporal.Temporal;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -32,7 +23,14 @@ import java.util.function.Supplier;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
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.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
import org.alfresco.repo.cache.SimpleCache;
|
import org.alfresco.repo.cache.SimpleCache;
|
||||||
import org.alfresco.repo.dictionary.M2Model;
|
import org.alfresco.repo.dictionary.M2Model;
|
||||||
@@ -50,9 +48,9 @@ import org.alfresco.service.namespace.QName;
|
|||||||
import org.alfresco.service.transaction.TransactionService;
|
import org.alfresco.service.transaction.TransactionService;
|
||||||
import org.alfresco.util.Pair;
|
import org.alfresco.util.Pair;
|
||||||
import org.apache.activemq.ActiveMQConnectionFactory;
|
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||||
|
import org.apache.activemq.jms.pool.PooledConnectionFactory;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.messaginghub.pooled.jms.JmsPoolConnectionFactory;
|
|
||||||
import org.quartz.JobKey;
|
import org.quartz.JobKey;
|
||||||
import org.quartz.Scheduler;
|
import org.quartz.Scheduler;
|
||||||
import org.quartz.SchedulerException;
|
import org.quartz.SchedulerException;
|
||||||
@@ -62,8 +60,6 @@ import org.quartz.impl.JobDetailImpl;
|
|||||||
import org.quartz.impl.StdSchedulerFactory;
|
import org.quartz.impl.StdSchedulerFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.DisposableBean;
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
@@ -81,56 +77,146 @@ import com.inteligr8.alfresco.annotations.job.AsyncJob;
|
|||||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
||||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import jakarta.annotation.PreDestroy;
|
|
||||||
import jakarta.jms.Connection;
|
|
||||||
import jakarta.jms.JMSException;
|
|
||||||
import jakarta.jms.Message;
|
|
||||||
import jakarta.jms.MessageConsumer;
|
|
||||||
import jakarta.jms.MessageProducer;
|
|
||||||
import jakarta.jms.Queue;
|
|
||||||
import jakarta.jms.Session;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides integration with MQ for the asynchronous method executions.
|
* This class provides integration with MQ for the asynchronous method executions.
|
||||||
*
|
*
|
||||||
* @author brian@inteligr8.com
|
* @author brian@inteligr8.com
|
||||||
*/
|
*/
|
||||||
@Component("async.mq")
|
@Component("async.mq")
|
||||||
public class MqAsyncService extends AbstractMqAsyncService {
|
public class MqAsyncService extends AbstractLifecycleBean implements AsyncService, Threadable {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
|
private final JobKey jobKey = new JobKey("mq-async", "inteligr8-annotations");
|
||||||
private JmsPoolConnectionFactory factory;
|
private final Pattern typePattern = Pattern.compile("v([0-9]+):([^:#]+)#(.+)");
|
||||||
|
private final ObjectMapper om = new ObjectMapper();
|
||||||
|
|
||||||
/**
|
@Value("${inteligr8.async.mq.enabled}")
|
||||||
* @PostConstruct does not work in ACS
|
protected boolean enabled;
|
||||||
*/
|
|
||||||
@PostConstruct
|
@Value("${inteligr8.async.mq.workerThreads}")
|
||||||
protected void init() {
|
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 PooledConnectionFactory factory;
|
||||||
|
|
||||||
|
private SimpleCache<Pair<Class<?>, String>, Method> methodCache;
|
||||||
|
|
||||||
|
private ThreadLocal<Boolean> isAsync = ThreadLocal.withInitial(new Supplier<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public Boolean get() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBootstrap(ApplicationEvent event) {
|
||||||
if (!this.enabled)
|
if (!this.enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
super.init();
|
try {
|
||||||
|
this.hostname = InetAddress.getLocalHost().getHostName();
|
||||||
|
} catch (UnknownHostException uhe) {
|
||||||
|
this.hostname = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url);
|
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(this.url);
|
||||||
|
|
||||||
JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory();
|
PooledConnectionFactory pool = new PooledConnectionFactory();
|
||||||
pool.setConnectionFactory(factory);
|
pool.setConnectionFactory(factory);
|
||||||
pool.setMaxConnections(this.maxConnections);
|
pool.setMaxConnections(this.maxConnections);
|
||||||
pool.start();
|
pool.start();
|
||||||
|
|
||||||
this.factory = pool;
|
this.factory = pool;
|
||||||
|
|
||||||
|
if (this.workerThreads <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||||
|
jobDetail.setKey(this.jobKey);
|
||||||
|
jobDetail.setRequestsRecovery(true);
|
||||||
|
jobDetail.setJobClass(AsyncJob.class);
|
||||||
|
jobDetail.getJobDataMap().put("asyncService", this);
|
||||||
|
|
||||||
|
Trigger trigger = TriggerBuilder.newTrigger()
|
||||||
|
.startNow()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
StdSchedulerFactory.getDefaultScheduler()
|
||||||
|
.scheduleJob(jobDetail, trigger);
|
||||||
|
} catch (SchedulerException se) {
|
||||||
|
this.logger.error("The MQ async service job failed to start; no asynchronous executions will be processed!", se);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@Override
|
||||||
protected void uninit() {
|
protected void onShutdown(ApplicationEvent event) {
|
||||||
super.uninit();
|
try {
|
||||||
|
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||||
|
scheduler.deleteJob(this.jobKey);
|
||||||
|
} catch (SchedulerException se) {
|
||||||
|
this.logger.warn("The MQ async service job failed to stop", se);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.factory != null)
|
if (this.factory != null)
|
||||||
this.factory.stop();
|
this.factory.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getThreads() {
|
||||||
|
return this.workerThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrentThreadAsynchronous() {
|
||||||
|
return this.isAsync.get();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void poll() throws AsyncProcessException {
|
public void poll() throws AsyncProcessException {
|
||||||
@@ -307,6 +393,43 @@ public class MqAsyncService extends AbstractMqAsyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Method findMethod(Class<?> clazz, String methodName) {
|
||||||
|
Pair<Class<?>, String> key = new Pair<>(clazz, methodName);
|
||||||
|
Method method = this.methodCache.get(key);
|
||||||
|
if (method != null) {
|
||||||
|
this.logger.trace("Found method in cache: {}", method);
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.trace("Looping through bean type methods to find: {}", methodName);
|
||||||
|
|
||||||
|
for (Method amethod : clazz.getDeclaredMethods()) {
|
||||||
|
if (amethod.getName().equals(methodName)) {
|
||||||
|
this.logger.debug("Found and caching method: {} => {}", key, amethod);
|
||||||
|
this.methodCache.put(key, amethod);
|
||||||
|
return amethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("The bean (" + clazz + ") does not implement the method: " + methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(ProceedingJoinPoint joinPoint) throws AsyncProcessException {
|
||||||
|
this.logger.trace("push({})", joinPoint);
|
||||||
|
|
||||||
|
if (!(joinPoint.getSignature() instanceof MethodSignature))
|
||||||
|
throw new IllegalStateException("The join point must be on methods and methods have signatures");
|
||||||
|
|
||||||
|
Object bean = joinPoint.getThis();
|
||||||
|
this.logger.debug("Queuing for bean: {}", bean.getClass());
|
||||||
|
|
||||||
|
MethodSignature methodSig = (MethodSignature) joinPoint.getSignature();
|
||||||
|
Method method = methodSig.getMethod();
|
||||||
|
this.logger.debug("Queuing for method: {}", method);
|
||||||
|
|
||||||
|
this.push(bean, method.getName(), Arrays.asList(joinPoint.getArgs()));
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException {
|
public void push(Object callbackBean, String callbackMethod, List<Object> args) throws AsyncProcessException {
|
||||||
this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args);
|
this.logger.trace("push({}, {}, {})", callbackBean.getClass(), callbackMethod, args);
|
||||||
@@ -350,5 +473,100 @@ public class MqAsyncService extends AbstractMqAsyncService {
|
|||||||
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
throw new AsyncProcessException("A JMS messaging issue occurred", je);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked" })
|
||||||
|
private Object unmarshal(Parameter param, Object arg) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||||
|
Class<?> paramType = param.getType();
|
||||||
|
this.logger.trace("Unmarshaling parameter of type: {}", paramType);
|
||||||
|
|
||||||
|
if (Version.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
||||||
|
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
|
||||||
|
|
||||||
|
Map<String, Serializable> versionPropertiesMap = (Map<String, Serializable>) argMap.get("properties");
|
||||||
|
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
|
||||||
|
|
||||||
|
Version version = new VersionImpl(versionPropertiesMap, nodeRef);
|
||||||
|
this.logger.trace("Unmarshaled version: {} = {}", param.getName(), version);
|
||||||
|
return version;
|
||||||
|
} else if (Action.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
||||||
|
Map<String, Object> argMap = (Map<String, Object>) this.om.convertValue(arg, Map.class);
|
||||||
|
|
||||||
|
String actionId = (String) argMap.get("actionId");
|
||||||
|
NodeRef nodeRef = new NodeRef((String) argMap.get("nodeRef"));
|
||||||
|
this.logger.trace("Unmarshaling action: {}, {}", actionId, nodeRef);
|
||||||
|
|
||||||
|
Action action = this.actionService.getAction(nodeRef, actionId);
|
||||||
|
this.logger.trace("Unmarshaled action: {} = {}", param.getName(), action);
|
||||||
|
return action;
|
||||||
|
} else if (Collection.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as JSON array: {}", arg);
|
||||||
|
return this.om.convertValue(arg, Collection.class);
|
||||||
|
} else if (Map.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as JSON object: {}", arg);
|
||||||
|
return this.om.convertValue(arg, Map.class);
|
||||||
|
} else if (QName.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as QName: {}", arg);
|
||||||
|
return QName.createQName((String) arg);
|
||||||
|
} else if (Enum.class.isAssignableFrom(paramType)) {
|
||||||
|
this.logger.trace("Unmarshaling as Enum: {}", arg);
|
||||||
|
Method cons = paramType.getDeclaredMethod("valueOf", String.class);
|
||||||
|
return cons.invoke(null, arg.toString());
|
||||||
|
} else {
|
||||||
|
this.logger.trace("Unmarshaling as POJO: {}", arg);
|
||||||
|
Constructor<?> cons = paramType.getConstructor(String.class);
|
||||||
|
return cons.newInstance(arg.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object marshal(Object arg) {
|
||||||
|
if (arg instanceof Version) {
|
||||||
|
Version version = (Version) arg;
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("nodeRef", version.getFrozenStateNodeRef());
|
||||||
|
map.put("properties", version.getVersionProperties());
|
||||||
|
|
||||||
|
this.logger.trace("Marshaling Version as JSON object: {}", map);
|
||||||
|
return this.om.convertValue(map, String.class);
|
||||||
|
} else if (arg instanceof Action) {
|
||||||
|
Action action = (Action) arg;
|
||||||
|
Map<String, Object> map = new HashMap<>();
|
||||||
|
map.put("nodeRef", action.getNodeRef());
|
||||||
|
map.put("actionId", action.getId());
|
||||||
|
|
||||||
|
this.logger.trace("Marshaling Action as JSON object: {}", map);
|
||||||
|
return this.om.convertValue(map, String.class);
|
||||||
|
} else if (arg instanceof Collection<?>) {
|
||||||
|
List<Object> list = new ArrayList<>(((Collection<?>)arg).size());
|
||||||
|
for (Object obj : (Collection<?>) arg)
|
||||||
|
list.add(this.marshal(obj));
|
||||||
|
|
||||||
|
this.logger.trace("Marshaling Java Collection as JSON array: {}", list);
|
||||||
|
return this.om.convertValue(list, String.class);
|
||||||
|
} else if (arg instanceof Map<?, ?>) {
|
||||||
|
Map<Object, Object> map = new HashMap<>();
|
||||||
|
for (Entry<?, ?> entry : ((Map<?, ?>) arg).entrySet()) {
|
||||||
|
Object key = this.marshal(entry.getKey());
|
||||||
|
Object value = this.marshal(entry.getValue());
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.trace("Marshaling Java Map as JSON object: {}", map);
|
||||||
|
return this.om.convertValue(map, String.class);
|
||||||
|
} else {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private M2Model loadModel(NodeRef nodeRef) throws IOException {
|
||||||
|
ContentReader creader = this.contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
||||||
|
InputStream istream = creader.getContentInputStream();
|
||||||
|
try {
|
||||||
|
return M2Model.createModel(istream);
|
||||||
|
} finally {
|
||||||
|
istream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,6 @@ import org.quartz.impl.JobDetailImpl;
|
|||||||
import org.quartz.impl.StdSchedulerFactory;
|
import org.quartz.impl.StdSchedulerFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.ApplicationEvent;
|
import org.springframework.context.ApplicationEvent;
|
||||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||||
@@ -33,8 +32,6 @@ import com.inteligr8.alfresco.annotations.job.AsyncJob;
|
|||||||
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
import com.inteligr8.alfresco.annotations.service.AsyncProcessException;
|
||||||
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
import com.inteligr8.alfresco.annotations.service.AsyncService;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides a non-persistent alternative to MQ for asynchronous method
|
* This class provides a non-persistent alternative to MQ for asynchronous method
|
||||||
* execution.
|
* execution.
|
||||||
@@ -42,7 +39,7 @@ import jakarta.annotation.PostConstruct;
|
|||||||
* @author brian@inteligr8.com
|
* @author brian@inteligr8.com
|
||||||
*/
|
*/
|
||||||
@Component("async.thread")
|
@Component("async.thread")
|
||||||
public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService, InitializingBean {
|
public class ThreadPoolAsyncService extends AbstractLifecycleBean implements AsyncService {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||||
private final JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations");
|
private final JobKey jobKey = new JobKey("thread-async", "inteligr8-annotations");
|
||||||
@@ -64,19 +61,9 @@ public class ThreadPoolAsyncService extends AbstractLifecycleBean implements Asy
|
|||||||
});
|
});
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @PostConstruct doesn't work in ACS for whatever reason
|
|
||||||
*/
|
|
||||||
@PostConstruct
|
|
||||||
protected void init() {
|
|
||||||
this.queue = new LinkedBlockingQueue<>(this.queueSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onBootstrap(ApplicationEvent event) {
|
protected void onBootstrap(ApplicationEvent event) {
|
||||||
|
this.queue = new LinkedBlockingQueue<>(this.queueSize);
|
||||||
|
|
||||||
JobDetailImpl jobDetail = new JobDetailImpl();
|
JobDetailImpl jobDetail = new JobDetailImpl();
|
||||||
jobDetail.setKey(this.jobKey);
|
jobDetail.setKey(this.jobKey);
|
||||||
jobDetail.setRequestsRecovery(true);
|
jobDetail.setRequestsRecovery(true);
|
||||||
@@ -6,14 +6,13 @@
|
|||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.ThreadedAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.ThreadedAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.AsyncAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.AuthorizedAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.IfAuthorizedAspect" />
|
|
||||||
|
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.JobLockAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.JobLockAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.OperableNodeAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeTypeAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeTypeAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.NodeAspectAspect" />
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.ChildIsPrimaryAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.ChildIsPrimaryAspect" />
|
||||||
|
|
||||||
<aspect name="com.inteligr8.alfresco.annotations.aspect.WebScriptAspect" />
|
<aspect name="com.inteligr8.alfresco.annotations.aspect.RetryingTransactionAspect" />
|
||||||
</aspects>
|
</aspects>
|
||||||
</aspectj>
|
</aspectj>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
module.id=${project.groupId}.${project.artifactId}
|
module.id=${project.groupId}.${project.artifactId}
|
||||||
module.alias=${project.groupId}.annotations-platform-module
|
|
||||||
module.title=${project.name}
|
module.title=${project.name}
|
||||||
module.description=${project.description}
|
module.description=${project.description}
|
||||||
module.version=${module.version}
|
module.version=${project.version}
|
||||||
|
|
||||||
module.repo.version.min=6.0
|
module.repo.version.min=6.0
|
||||||
#module.repo.version.max=
|
#module.repo.version.max=
|
||||||
|
|
||||||
module.depends.com.inteligr8.alfresco.aspectj-platform-module=1.0-*
|
module.depends.aspectj-platform-module=1.0-*
|
||||||
@@ -44,8 +44,9 @@ public class TransactionalTest extends AbstractLifecycleBean {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.tryNoSupportsTransactional();
|
this.tryNoSupportsTransactional();
|
||||||
} catch (IllegalTransactionStateException uoe) {
|
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
} catch (IllegalTransactionStateException uoe) {
|
||||||
|
// suppress
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -199,8 +200,7 @@ public class TransactionalTest extends AbstractLifecycleBean {
|
|||||||
|
|
||||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||||
private void tryNoSupportsTransactional() {
|
private void tryNoSupportsTransactional() {
|
||||||
Assert.isNull(AlfrescoTransactionSupport.getTransactionId(), "Expected no transaction");
|
throw new UnsupportedOperationException();
|
||||||
Assert.isTrue(TxnReadState.TXN_NONE.equals(AlfrescoTransactionSupport.getTransactionReadState()), "Expected not transaction");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(propagation = Propagation.NEVER)
|
@Transactional(propagation = Propagation.NEVER)
|
||||||
Reference in New Issue
Block a user