Automatically register XA Resource Spring Boot - spring

I'm trying to implement XA transactions in my Spring Boot app across Hazelcast and JPA persisting to PostgreSQL. Putting the Atomikos Spring Boot starter in my pom.xml got it to load the JtaTransactionManager to be used with the #Transactional annotations, but the Hazelcast XA Resource is not being enlisted with the transaction.
How do I get Spring Boot to automatically enlist my XA Resources with the JTA UserTransaction as part of the AOP transaction interceptor that's using the JtaTransactionManager?

I solved this by using an annotation and AspectJ Aspect as described here. Also see this for defining the pointcuts to match either class or method level annotations, and you may need to do this:
#EnableTransactionManagement(order = Ordered.HIGHEST_PRECEDENCE)
to have the transaction interceptor happen before this code is called.
#Aspect
#Component
public class XAResourceAspect {
#Autowired
JtaTransactionManager jtaTransactionManager;
#Autowired
ApplicationContext applicationContext;
#Pointcut("within(#XAResource *)")
public void beanAnnotatedWithAnnotation() {}
#Pointcut("execution(public * *(..))")
public void publicMethod() {}
#Pointcut("publicMethod() && beanAnnotatedWithAnnotation()")
public void publicMethodInsideAnnotatedClass() {}
private ThreadLocal<Map<Transaction, Set<String>>> enlistedResources = new ThreadLocal<>();
#Around("#annotation(ppi.nestup.v3.annotation.XAResource) || publicMethodInsideAnnotatedClass()")
public Object enlistResources(ProceedingJoinPoint joinPoint) throws Throwable {
boolean setThreadLocal = false;
Transaction transaction = jtaTransactionManager.getTransactionManager().getTransaction();
if (transaction != null) {
Map<Transaction, Set<String>> transactionMap = enlistedResources.get();
LOG.info("Enlisting resources for joinpoint " + joinPoint + " and transaction " + transaction);
if (transactionMap == null) {
transactionMap = new HashMap<>();
enlistedResources.set(transactionMap);
setThreadLocal = true;
LOG.info("Created new ThreadLocal for transaction " + transaction);
} else {
LOG.info("Found existing ThreadLocal " + transactionMap);
}
transactionMap.computeIfAbsent(transaction, k -> new HashSet<>());
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Class withinType = joinPoint.getSourceLocation().getWithinType();
XAResource annotation = method.getAnnotation(XAResource.class);
if (annotation == null) {
annotation = (XAResource) withinType.getAnnotation(XAResource.class);
}
String[] resourceNames = annotation.value();
for (String name : resourceNames) {
if (!transactionMap.get(transaction).contains(name)) {
javax.transaction.xa.XAResource resource =
(javax.transaction.xa.XAResource) applicationContext.getBean(name);
try {
transaction.enlistResource(resource);
} catch (IllegalStateException e) {
LOG.error("Caught exception trying to enlist resource " + name + " for transaction " + transaction + " and joinpoint " + joinPoint);
e.printStackTrace();
}
transactionMap.get(transaction).add(name);
}
}
}
Object proceed = joinPoint.proceed();
if (setThreadLocal) {
LOG.info("Removing threadlocal");
enlistedResources.remove();
}
return proceed;
}
}
I haven't done a lot of testing of this yet, but it's working so far.

Related

Is there a way to make a transactional action in a #Scheduled method?

So, my application is multi-tenants based, and I need to apply a transactional request each Sunday at 1:00 PM.
It basically needs to get All resources and create Usage (which is an entity per week) based on the actual Date.
My transactional Method :
/**
* Scheduled to run at 1:00 every sunday
* It should create capacities for all resources that doesn't have any for next years
*/
#Transactional
public void createNewCapacities() throws Exception {
LocalDate now = LocalDate.now();
System.out.println("Date is :" + now);
System.out.println("Start of capacities creation...");
//List<Resource> resources = resourceService.findAll();
//for(Resource resource : resources){
//Calendar calendar = calendarService.findCalendarById(resource.getSelectedCalendarId());
...
//}
}
My scheduler :
#Service
public class UsageServiceScheduler {
#Autowired
private UsageService usageService;
#Scheduled(cron= "0 0 1 * * SUN")
public void callScheduledTask() throws Exception {
usageService.createNewCapacities();
}
}
This throw an Exception:
org.springframework.transaction.CannotCreateTransactionException:
Could not open JPA EntityManager for transaction; nested exception is
java.lang.IllegalStateException: Cannot determine target DataSource
for lookup key [null].
Is there a way to establish a connection with the database during the #Scheduled method?
EDIT :
I have both #Transactional and #Scheduled enabled.
TENANT DATA SOURCE PROPERTIES :
#Component
#ConfigurationProperties(prefix = "tenants")
public class TenantDataSourceProperties {
private Map<Object, Object> dataSources = new LinkedHashMap<>();
public Map<Object, Object> getDataSources() {
return dataSources;
}
public void setDataSources(Map<String, Map<String, String>> dataSources) {
dataSources.forEach((key, value) -> this.dataSources.put(key, convert(value)));
}
public DataSource convert(Map<String, String> source) {
return DataSourceBuilder.create()
.url(source.get("jdbcUrl"))
.driverClassName(source.get("driverClassName"))
.username(source.get("username"))
.password(source.get("password"))
.build();
}
}
I want my cron job to run for all existing tenants.
Or in other words, get all the dataSources and apply cron job for each db.

How to Method#getAnnotatedParameterTypes() in spring proxied class

I'm using spring-boot 2+ and created some custom annotation;
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation{
}
When doing:
final AnnotatedType[] annotatedTypes = mostSpecificMethod.getAnnotatedParameterTypes();
//this will get the original class
//final Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
Class<?> annotatedMappedClass = null;
for (AnnotatedType annotatedType : annotatedTypes) {
if (annotatedType.isAnnotationPresent(MyCustomAnnotation.class)) {
annotatedMappedClass = TypeFactory.rawClass(annotatedType.getType());
}
}
it works when bean is not a proxy but when I add the #Transactional annotation it becomes a proxy and stops working. What is the Spring Util to find in the target class?
As far as I understood you'll need the bean. Using:
Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
seems to work.
Also a more complex one:
Method method = mostSpecificMethod;
if (AopUtils.isAopProxy(bean)) {
try {
Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
method = clazz.getMethod(mostSpecificMethod.getName(), mostSpecificMethod.getParameterTypes());
}
catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("...", ex);
}
}

Hibernate session closed for no apparent reason

I've been struggling with this for days and i really don't know what's happening here.
I have a few processes that are triggered by a user and happen on a seperate thread, which does some operations, and updates an entry in the DB about it's progress which the user can retrieve.
this has all been working fine until recently, when suddenly, sometimes, seemingly uncorrelated with anything, these processes fail on their first attempt to lazy load an entity. They fail with one of a few different errors, all of which eventually stem from the hibernate seesion being closed somehow:
org.hibernate.SessionException: Session is closed. The read-only/modifiable setting is only accessible when the proxy is associated with an open session.
-
org.hibernate.SessionException: Session is closed!
-
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed
-
org.hibernate.exception.GenericJDBCException: Could not read entity state from ResultSet : EntityKey[com.rdthree.plenty.domain.crops.CropType#1]
-
java.lang.NullPointerException
at com.mysql.jdbc.ResultSetImpl.checkColumnBounds(ResultSetImpl.java:766)
i'm using spring #Transactional to manage my transactions
here's my config:
#Bean
public javax.sql.DataSource dataSource() {
// update TTL so that the datasource will pick up DB failover - new IP
java.security.Security.setProperty("networkaddress.cache.ttl", "30");
HikariDataSource ds = new HikariDataSource();
String jdbcUrl = "jdbc:mysql://" + plentyConfig.getHostname() + ":" + plentyConfig.getDbport() + "/"
+ plentyConfig.getDbname() + "?useSSL=false";
ds.setJdbcUrl(jdbcUrl);
ds.setUsername(plentyConfig.getUsername());
ds.setPassword(plentyConfig.getPassword());
ds.setConnectionTimeout(120000);
ds.setMaximumPoolSize(20);
return ds;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager manager = new JpaTransactionManager();
manager.setEntityManagerFactory(entityManagerFactory);
return manager;
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(javax.sql.DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.rdthree.plenty.domain");
Properties properties = new Properties();
properties.put("org.hibernate.flushMode", "ALWAYS");
properties.put("hibernate.cache.use_second_level_cache", "true");
properties.put("hibernate.cache.use_query_cache", "true");
properties.put("hibernate.cache.region.factory_class",
"com.rdthree.plenty.config.PlentyInfinispanRegionFactory");
properties.put("hibernate.cache.infinispan.statistics", "true");
properties.put("hibernate.cache.infinispan.query", "distributed-query");
properties.put("hibernate.enable_lazy_load_no_trans", "true");
if (plentyConfig.getProfile().equals(PlentyConfig.UNIT_TEST)
|| plentyConfig.getProfile().equals(PlentyConfig.PRODUCTION_INIT)) {
properties.put("hibernate.cache.infinispan.cfg", "infinispan-local.xml");
} else {
properties.put("hibernate.cache.infinispan.cfg", "infinispan.xml");
}
factory.setJpaProperties(properties);
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(false);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
factory.setJpaVendorAdapter(adapter);
return factory;
}
the way it works is that the thread fired by the user iterates over a collection of plans and applies each of them, the process that applies the plan also updates the progress entity on the DB.
the whole thread bean is marked at transactional:
#Component
#Scope("prototype")
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public class TemplatePlanApplicationThreadBean extends AbstractPlentyThread implements TemplatePlanApplicationThread {
...
#Override
public void run() {
startProcessing();
try {
logger.trace(
"---Starting plan manifestation for " + fieldCropReplaceDatePlantationDates.size() + " fields---");
List<Plan> plans = new ArrayList<>();
for (FieldCropReplaceDatePlantationDates obj : fieldCropReplaceDatePlantationDates) {
for (TemplatePlan templatePlan : obj.getTemplatePlans()) {
try {
plans.add(planService.findActivePlanAndManifestTemplatePlan(templatePlan, organization,
obj.getPlantationDate(), obj.getReplacementStartDate(), obj.getFieldCrop(),
autoSchedule, schedulerRequestArguments, planApplicationProgress, false));
} catch (ActivityException e) {
throw new IllegalArgumentException(e);
}
}
Plan plan = plans.get(plans.size() - 1);
plan = planService.getEntityById(plan.getId());
if (plan != null) {
planService.setUnscheduledPlanAsSelected(plan);
}
plans.clear();
}
if (planApplicationProgressService.getEntityById(planApplicationProgress.getId()) != null) {
planApplicationProgressService.deleteEntity(planApplicationProgress.getId());
}
} catch (Exception e) {
logger.error(PlentyUtils.extrapolateStackTrace(e));
connector.createIssue("RdThreeLLC", "plenty-web",
new GitHubIssueRequest("Template plan application failure",
"```\n" + PlentyUtils.extrapolateStackTrace(e) + "\n```", 1, new ArrayList<>(),
Lists.newArrayList("plentytickets")));
planApplicationProgress.setFailed(true);
planApplicationProgressService.saveEntity(planApplicationProgress);
} finally {
endProcessing();
}
}
here is the method called by the thread:
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public synchronized Plan findActivePlanAndManifestTemplatePlan(TemplatePlan templatePlan,
ServiceProviderOrganization organization, Date plantationDate, Date replacementDate, FieldCrop fieldCrop,
boolean autoSchedule, SchedulerRequestArguments schedulerRequestArguments, Progress planProgress,
boolean commit) throws ActivityException {
Plan oldPlan = getLatestByFieldCrop(fieldCrop);
List<Activity> activitiesToRemove = oldPlan != null
? findActivitiesToRemoveAfterDate(oldPlan, replacementDate != null ? replacementDate : new Date())
: new ArrayList<>();
List<PlanExpense> planExpensesToRemove = oldPlan != null
? findPlanExpensesToRemoveAfterDate(oldPlan, replacementDate != null ? replacementDate : new Date())
: new ArrayList<>();
Date oldPlanPlantationDate = oldPlan != null ? inferPlanDates(oldPlan).getPlantationDate() : null;
if (oldPlan != null) {
if (commit) {
oldPlan.setReplaced(true);
}
buildPlanProfitProjectionForPlanAndField(oldPlan, Sets.newHashSet(activitiesToRemove),
Sets.newHashSet(planExpensesToRemove));
}
if (commit) {
for (Activity activity : activitiesToRemove) {
activityService.deleteEntity(activity.getId());
}
for (PlanExpense planExpense : planExpensesToRemove) {
planExpenseService.deleteEntity(planExpense.getId());
}
}
oldPlan = oldPlan != null ? getEntityById(oldPlan.getId()) : null;
Plan plan = manifestTemplatePlan(templatePlan, oldPlan, organization,
plantationDate != null ? plantationDate : oldPlanPlantationDate, replacementDate, fieldCrop,
autoSchedule, schedulerRequestArguments, planProgress, commit);
if (!commit) {
setPlanAllocationsUnscheduled(plan);
}
return plan;
}
the thing that kills me is that the error happens only sometimes, so i can't really debug it and i can't correlate it with anything.
Any ideas about what would cause the session to close?
i tried turning off any other threads, so basically this is the only one that's running, didn't help
Thanks

Spring boot custom Servlet doesn't map to bean name

I am trying to register a custom servlet.
I used this code in a #Configuration class:
#Bean (name="probe")
public PingServlet probe(){
return new PingServlet();
}
I thought this would be mapped to /probe, but it doesn't. I maps to '/' and the reason is that in class ServletContextInitializerBeans, there this method:
private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory,
Class<T> type, Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
List<Map.Entry<String, B>> beans = getOrderedBeansOfType(beanFactory, beanType,
this.seen);
for (Entry<String, B> bean : beans) {
if (this.seen.add(bean.getValue())) {
int order = getOrder(bean.getValue());
String beanName = bean.getKey();
// One that we haven't already seen
RegistrationBean registration = adapter.createRegistrationBean(beanName,
bean.getValue(), beans.size());
registration.setName(beanName);
registration.setOrder(order);
this.initializers.add(type, registration);
if (this.log.isDebugEnabled()) {
this.log.debug(
"Created " + type.getSimpleName() + " initializer for bean '"
+ beanName + "'; order=" + order + ", resource="
+ getResourceDescription(beanName, beanFactory));
}
}
}
}
The line List<Map.Entry<String, B>> beans = getOrderedBeansOfType(beanFactory, beanType, this.seen);, return list of 1 bean only (my servlet) although beanType is javax Servlet and I would expect DispatcherServlet to be there as well (I'm also using Spring MVC).
This results to an error in the following method (in class ServletRegistrationBeanAdapter):
#Override
public RegistrationBean createRegistrationBean(String name, Servlet source,
int totalNumberOfSourceBeans) {
String url = (totalNumberOfSourceBeans == 1 ? "/" : "/" + name + "/");
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/"; // always map the main dispatcherServlet to "/"
}
ServletRegistrationBean bean = new ServletRegistrationBean(source, url);
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
Since the beans list is of size 1, in the createRegistrationBean it hard codes the mapping to '/'.
This in turn causes they embedded jetty to fail starting as there are 2 mappings to '/' (DispatcherServlet and my PingServlet).
Any ideas what's going wrong here?
Thanks to #M. Deinum This works:
#Bean
public ServletRegistrationBean pingRegistration(PingServlet pingServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
pingServlet);
registration.addUrlMappings("/probe/*");
return registration;
}

LDAP Connection Pooling with spring security

I was trying setup LDAP connection pooling using spring security and xml based configuration.
Below is my configuration,
<authentication-manager id="authenticationManager">
<ldap-authentication-provider server-ref="ldapServer"
user-dn-pattern="uid={0},ou=users"
group-search-filter="(&(objectClass=groupOfUniqueNames)(uniqueMember={0}))"
group-search-base="ou=groups"
group-role-attribute="cn"
role-prefix="ROLE_"
user-context-mapper-ref="ldapContextMapperImpl">
</ldap-authentication-provider>
</authentication-manager>
How do i provide all the connection pooling configuration?
I am intending to use PoolingContextSource class as it provides properties to configure pool size etc.
They explicitly removed pooling for ldap binds (or in Spring's case an authenticate):
https://github.com/spring-projects/spring-ldap/issues/216
ldapTemplate.authenticate searches for the user and calls contextSource.getContext to perform the ldap bind.
private AuthenticationStatus authenticate(Name base,
String filter,
String password,
SearchControls searchControls,
final AuthenticatedLdapEntryContextCallback callback,
final AuthenticationErrorCallback errorCallback) {
List<LdapEntryIdentification> result = search(base, filter, searchControls, new LdapEntryIdentificationContextMapper());
if (result.size() == 0) {
String msg = "No results found for search, base: '" + base + "'; filter: '" + filter + "'.";
LOG.info(msg);
return AuthenticationStatus.EMPTYRESULT;
} else if (result.size() > 1) {
String msg = "base: '" + base + "'; filter: '" + filter + "'.";
throw new IncorrectResultSizeDataAccessException(msg, 1, result.size());
}
final LdapEntryIdentification entryIdentification = result.get(0);
try {
DirContext ctx = contextSource.getContext(entryIdentification.getAbsoluteName().toString(), password);
executeWithContext(new ContextExecutor<Object>() {
public Object executeWithContext(DirContext ctx) throws javax.naming.NamingException {
callback.executeWithContext(ctx, entryIdentification);
return null;
}
}, ctx);
return AuthenticationStatus.SUCCESS;
}
catch (Exception e) {
LOG.debug("Authentication failed for entry with DN '" + entryIdentification.getAbsoluteName() + "'", e);
errorCallback.execute(e);
return AuthenticationStatus.UNDEFINED_FAILURE;
}
}
By default, context sources disable pooling. From AbstractContextSource.java (which is what LdapContextSource inherits from):
public abstract class AbstractContextSource implements BaseLdapPathContextSource, InitializingBean {
...
public DirContext getContext(String principal, String credentials) {
// This method is typically called for authentication purposes, which means that we
// should explicitly disable pooling in case passwords are changed (LDAP-183).
return doGetContext(principal, credentials, EXPLICITLY_DISABLE_POOLING);
}
private DirContext doGetContext(String principal, String credentials, boolean explicitlyDisablePooling) {
Hashtable<String, Object> env = getAuthenticatedEnv(principal, credentials);
if(explicitlyDisablePooling) {
env.remove(SUN_LDAP_POOLING_FLAG);
}
DirContext ctx = createContext(env);
try {
authenticationStrategy.processContextAfterCreation(ctx, principal, credentials);
return ctx;
}
catch (NamingException e) {
closeContext(ctx);
throw LdapUtils.convertLdapException(e);
}
}
...
}
And if you try to use the PoolingContextSource, then you will get an UnsupportedOperationException when authenticate tries to call getContext:
public class PoolingContextSource
extends DelegatingBaseLdapPathContextSourceSupport
implements ContextSource, DisposableBean {
...
#Override
public DirContext getContext(String principal, String credentials) {
throw new UnsupportedOperationException("Not supported for this implementation");
}
}
This code is from the spring-ldap-core 2.3.1.RELEASE maven artifact.
You can still do connection pooling for ldap searches using the PoolingContextSource, but connection pooling for authenticates won't work.
Pooled connections doesn't work with authentication, because the way LDAP authentication works is that the connection is authenticated on creation.

Resources