Spring MVC: Multiple dataSources: select DB schema based on user selection from UI - spring

I'm working on a web app which works fine. But recently from management request we need to serve our web app to multiple teams. So what we decided is to create a separate copy of DB schema for each team.
Let's say our team uses TEAM_A schema & other team uses TEAM_B schema. Both DB schema's are exactly same only difference is data in them.
So I used AbstractRoutingDataSource in my application like this:
package com.company.app.utils.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return TeamContextHolder.getTeamType();
}
}
And TeamContextHolder as:
package com.company.app.utils.datasource;
public class TeamContextHolder{
public static final String TEAM_A_DATA_SOURCE = "aTeamDataSource";
public static final String TEAM_B_DATA_SOURCE = "bTeamDataSource";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setTeamType(String teamType) {
contextHolder.set(teamType);
}
public static String getTeamType() {
return contextHolder.get();
}
public static void clearTeamType() {
contextHolder.remove();
}
}
Spring configuration file have below bean definition for data-sources:
<bean id="aTeamDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="url" value="jdbc:mysql://localhost.com:3306/TEAM_A?autoReconnect=true&verifyServerCertificate=false&useSSL=true" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="welcome" />
<property name="password" value="welcome" />
</bean>
<bean id="bTeamDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
<property name="url" value="jdbc:mysql://localhost.com:3306/TEAM_B?autoReconnect=true&verifyServerCertificate=false&useSSL=true" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="username" value="welcome" />
<property name="password" value="welcome" />
</bean>
<bean primary="true" id="dynamicDataSource" class="com.company.app.utils.datasource.DynamicDataSource" >
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="aTeamDataSource" key="aTeamDataSource"></entry>
<entry value-ref="bTeamDataSource" key="bTeamDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="aTeamDataSource" >
</property>
</bean>
My home controller:
package com.company.app.web;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
import java.util.Map;
#Controller
#SessionAttributes("team")
public class Home {
/**
* Logger
*/
private static final Logger logger = Logger.getLogger(Home.class);
#RequestMapping(value="/home")
public ModelAndView mainHomePage() {
logger.info("Landing on main home page");
Map<String, Object> model = new HashMap<>();
return new ModelAndView("/home", "model", model);
}
#RequestMapping(value="/setteam")
public ModelAndView rememberTeam(#RequestParam String product) {
logger.info("Setting product info: " + product);
Map<String, Object> model = new HashMap<>();
model.put("team", team);
return new ModelAndView("/home", "model", model);
}
}
From home controller, I'm passing team session variable to other controllers via JSP file.
My home.jsp has below form:
<form action="./setproduct" method="POST" class="p-a-4">
<fieldset class="page-signup-form-group form-group form-group-lg">
<select class="page-signup-form-control form-control" id="grid-input-lg-2" name="product">
<option value="Not selected">Not selected</option>
<option value="TEAM_A">TEAM_A</option>
<option value="TEAM_B">TEAM_B</option>
</select>
</fieldset>
<button type="submit" class="btn btn-block btn-lg btn-primary m-t-3">Submit</button>
</form>
And then finally I'm setting datasource to use in my DaoImpl classes as:
if (filters.getTeam().equalsIgnoreCase("TEAM_B")){
TeamContextHolder.setTeamType(TeamContextHolder.TEAM_B_DATA_SOURCE);
}
And it works as expected as I thought. But blown in production as all the time TEAM_B datasource is selected.
So my questions:
Is it possible the on single tomcat webserver (where we deployed our
app) few users can select TEAM_A and work with TEAM_A_DATA_SOURCE
and few users select TEAM_B and work with TEAM_B_DATA_SOURCE?
Initially I thought it is doable but after this kaboom on production
I'm not sure if it's possible in spring.
If it's NOT possible then
how should I handle my case where end users can select their teams
and work simultaneous on two different DB schema's on same web-app.

I resolved this issue by changing contextHolder from ThreadLocal to HttpSession, that way I was able to connect to a team's DB schema for a entire browser session. Also now I don't have to pass session variable to other controllers and no need to set datasource to use in my DaoImpl classes as I was doing earlier. So now my updated TeamContextHolder looks like this:
package com.company.app.utils.datasource;
import org.apache.log4j.Logger;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpSession;
public class TeamContextHolder {
private static final Logger logger = Logger.getLogger(TeamContextHolder.class);
public static HttpSession getCurrentSession() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true);
}
public static void setTeamType(String teamType) {
TeamContextHolder.getCurrentSession().setAttribute("team", teamType);
}
public static String getTeamType() {
logger.info("Session attribute product: " + TeamContextHolder.getCurrentSession().getAttribute("team"));
return (String) TeamContextHolder.getCurrentSession().getAttribute("team");
}
}
Session variable is set from home controller as:
package com.company.app.web;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
#Controller
#SessionAttributes("team")
public class HomeController {
private static final Logger logger = Logger.getLogger(HomeController.class);
#RequestMapping(value="/home")
public ModelAndView home() {
logger.info("Landing on main home page");
Map<String, Object> model = new HashMap<>();
return new ModelAndView("/home", "model", model);
}
#RequestMapping(value="/setteam")
public ModelAndView setteam(#RequestParam String team,
HttpServletRequest request) {
// Setting session attribute:
request.getSession().setAttribute("team", team);
logger.info("Setting team: " + team);
Map<String, Object> model = new HashMap<>();
model.put("team", team);
return new ModelAndView("/home", "model", model);
}
}

You can have multiple entity managers which will be scanning the different set of repositories and you can switch those entity managers depending upon your cookie.

Related

SimpleRabbitListenerContainerFactory.setAdviceChain: can MethodInterceptor accept POJO that parameterizes #RabbitListener?

Versions:
org.springframework.amqp:spring-rabbit:2.4.8
openjdk version "11.0.12" 2021-07-20
XML:
<bean id="myAdvice" class="com.acme.interceptors.TrackNewBarcode"/>
<bean id="rmqMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter">
<constructor-arg ref="myObjectMapper"/>
</bean>
<bean id="rabbitListenerContainerFactory" class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="concurrentConsumers" value="1"/>
<property name="maxConcurrentConsumers" value="1"/>
<property name="receiveTimeout" value="316224000000"/>
<property name="messageConverter" ref="rmqMessageConverter"/>
<property name="adviceChain" ref="myAdvice"/>
</bean>
Java:
package com.acme.tasks;
import com.acme.io.NewBarcodes;
import javax.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Validated
#Component
public class ConsumeBarcodes
{
private static final Logger logger = LoggerFactory.getLogger(ConsumeBarcodes.class);
#Value("${rabbitmq.queue}")
private String queueName;
public ConsumeBarcodes() {}
#RabbitListener(queues = "${rabbitmq.queue}", ackMode = "AUTO")
public void ingestNewBarcodes(#NotNull NewBarcodes newBarcodes)
{
logger.debug("RECEIVED message in ingestNewBarcodes from RabbitMQ queue: {}", queueName);
// XXX Process newBarcodes here.
}
}
Advice:
package com.acme.interceptors;
...
public class TrackNewBarcode implements MethodInterceptor
{
...
#Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable
{
NewBarcodes newBarcodes = (NewBarcodes)methodInvocation.getArguments()[1];
...
}
}
In the examples I have seen for #RabbitListener advices, in invoke(), argument #1 is cast to a Message. But in my #RabbitListener, I rely on a Jackson message-converter to convert the inbound parameter into the POJO NewBarcodes.
QUESTION: in my advice here, can I cast argument #1 to the POJO NewBarcodes?
The simple answer is No. methodInvocation.getArguments() returns a Channel in the first argument (index 0), and a Message in the second argument (index 1). Casting a Message to NewBarcodes would lead to an exception.
Nevertheless, there are still other ways to encode an advice here. I spell out one approach below.
First, per Spring AOP docs, you should enable AOP functionality in your application configuration. Eg:
#Configuration
#EnableAspectJAutoProxy
public class MyWebAppConfig implements WebMvcConfigurer
{
...
}
Annotate ConsumeBarcodes with #Aspect, and set up #Pointcut and #After advice:
#Aspect
#Validated
#Component
public class ConsumeBarcodes
{
private static final Logger logger = LoggerFactory.getLogger(ConsumeBarcodes.class);
#Value("${rabbitmq.queue}")
private String queueName;
public ConsumeBarcodes() {}
#RabbitListener(queues = "${rabbitmq.queue}", ackMode = "AUTO")
public void ingestNewBarcodes(#NotNull NewBarcodes newBarcodes)
{
logger.debug("RECEIVED message in ingestNewBarcodes from RabbitMQ queue: {}", queueName);
// XXX Process newBarcodes here.
}
#Pointcut("execution(com.acme.tasks.ConsumeBarcodes.ingestNewBarcodes(..)")
public void newBarcodesIn() {}
#After("newBarcodesIn()")
public void ackNewBarcodesIn(JoinPoint jp)
{
NewBarcodes newBarcodes = (NewBarcodes)jp.getArgs()[0];
...
}
}
Arguably, this is overkill, as we could simply put logic from ackNewBarcodesIn() inside ingestNewBarcodes() and forgo advices altogether. Nevertheless, I have confirmed this setup works and the #After advice fires as expected.

How to pass a Map<String, String> with application.properties

I have implemented some authorization in a webservice that needs be configured.
Currently the user/password combo is hardcoded into the bean configuration. I would like to configure the map with users and passwords into the application.properties so the configuration can be external.
Any clue on how this can be done?
<bean id="BasicAuthorizationInterceptor" class="com.test.BasicAuthAuthorizationInterceptor">
<property name="users">
<map>
<entry key="test1" value="test1"/>
<entry key="test2" value="test2"/>
</map>
</property>
</bean>
you can use #Value.
Properties file:
users={test1:'test1',test2:'test2'}
Java code:
#Value("#{${users}}")
private Map<String,String> users;
You can use #ConfigurationProperties to have values from application.properties bound into a bean. To do so you annotate your #Bean method that creates the bean:
#Bean
#ConfigurationProperties
public BasicAuthAuthorizationInterceptor interceptor() {
return new BasicAuthAuthorizationInterceptor();
}
As part of the bean's initialisation, any property on BasicAuthAuthorizationInterceptor will be set based on the application's environment. For example, if this is your bean's class:
public class BasicAuthAuthorizationInterceptor {
private Map<String, String> users = new HashMap<String, String>();
public Map<String, String> getUsers() {
return this.users;
}
}
And this is your application.properties:
users.alice=alpha
users.bob=bravo
Then the users map will be populated with two entries: alice:alpha and bob:bravo.
Here's a small sample app that puts this all together:
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableAutoConfiguration
#EnableConfigurationProperties
public class Application {
public static void main(String[] args) throws Exception {
System.out.println(SpringApplication.run(Application.class, args)
.getBean(BasicAuthAuthorizationInterceptor.class).getUsers());
}
#Bean
#ConfigurationProperties
public BasicAuthAuthorizationInterceptor interceptor() {
return new BasicAuthAuthorizationInterceptor();
}
public static class BasicAuthAuthorizationInterceptor {
private Map<String, String> users = new HashMap<String, String>();
public Map<String, String> getUsers() {
return this.users;
}
}
}
Take a look at the javadoc for ConfigurationProperties for more information on its various configuration options. For example, you can set a prefix to divide your configuration into a number of different namespaces:
#ConfigurationProperties(prefix="foo")
For the binding to work, you'd then have to use the same prefix on the properties declared in application.properties:
foo.users.alice=alpha
foo.users.bob=bravo
A java.util.Properties object is already a Map, actually a HashTable which in turn implements Map.
So when you create a properties file (lets name it users.properties) you should be able to load it using a PropertiesFactoryBean or <util:properties /> and inject it into your class.
test1=test1
test2=test2
Then do something like
<util:properties location="classpath:users.properties" id="users" />
<bean id="BasicAuthorizationInterceptor" class="com.test.BasicAuthAuthorizationInterceptor">
<property name="users" ref="users" />
</bean>
Although if you have a Map<String, String> as a type of the users property it might fail... I wouldn't put them in the application.properties file. But that might just be me..
I think you are looking for something similar
http://www.codejava.net/frameworks/spring/reading-properties-files-in-spring-with-propertyplaceholderconfigurer-bean
You can pick values from .properties similarly and assign it to your map.

Using values from properties file in spring roo

I'm new to spring and spring-roo. I try to build an application and read some key value pairs from a properties file.
I created a myconfig.properties file and saved it to src/main/resources/META-INF/spring/.
The content of the file is:
## My Configuration settings
myconfig.url=https://1.2.3.4/api.php
myconfig.username=user1
myconfig.password=password1
Now I added a bean configuration into appilcationContext.xml in the same directory
<bean id="MyConfig" class="com.test.client.MyClient">
<property name="url" value="${myconfig.url}" />
<property name="username" value="${myconfig.username}" />
<property name="password" value="${myconfig.password}" />
</bean>
In my class file I tried to access the values, but I get an File not found error
package com.test.client;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
#RooJavaBean
#RooToString
#RooJpaActiveRecord
public class MyClient {
private String url;
private String username;
private String password;
public static String login()
{
// Construct the spring application context
AbstractApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyClient config = (MyClient) context.getBean("MyConfig");
// Register hook to shutdown Spring gracefully
// See http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-factory-shutdown
context.registerShutdownHook();
String token = null;
final String url = config.getUrl();
final String username = config.getUsername();
final String password = config.getPassword();
....
Thanks for any help!
Try
AbstractApplicationContext context = new ClassPathXmlApplicationContext("classpath*:META-INF/spring/applicationContext.xml");
However the best practice is to implement interface ApplicationContextAware.
Stefano

Spring MBeanExporter - giving name to MBean

I'm trying to run a simple application with jmx-exported method. I do it like (spring-context and cglib for "#Configuration" are in classpath):
package com.sopovs.moradanen.jmx;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.stereotype.Component;
#Component
#Configuration
public class SpringJmxTest {
public static void main(String[] args) {
new AnnotationConfigApplicationContext("com.sopovs.moradanen.jmx");
while (true) {
Thread.yield();
}
}
#Bean
public MBeanExporter createJmxExporter() {
return new MBeanExporter();
}
public interface FooBarMBean {
public String hello();
}
#Component
public static class FooBar implements FooBarMBean {
#Override
public String hello() {
return "Hello";
}
}
}
However when I run it I get:javax.management.MalformedObjectNameException: Key properties cannot be empty. I tried to debug and solved it with:
#Component
public static class FooBar implements FooBarMBean, SelfNaming {
#Override
public String hello() {
return "Hello";
}
#Override
public ObjectName getObjectName() throws MalformedObjectNameException {
return new ObjectName("fooBar:name=" + getClass().getName());
}
}
But is there a better way to supply a name for MBean?
You can use the descriptions annotations provided by Spring Context #Managed* :
To do this, you must NOT implements the interface with "MBean" or "MXBean" suffix, neither SelfNaming.
Then, the bean will be detected as a standard spring "managed bean" when MBeanExporter will registerBeanInstance(..), and will be converted to a ModelMBean using all spring annotations, including descriptions of attributes, operations, parameters, etc..
As a requirement, you should declare in your spring context the MBeanExporter with AnnotationJmxAttributeSource, MetadataNamingStrategy, and MetadataMBeanInfoAssembler attributes, which can be simplified like this (as described here):
<bean id="mbeanExporter"
class="org.springframework.jmx.export.annotation.AnnotationMBeanExporter" />
or:
<context:mbean-export />
or, using programmatic approach:
#Configuration
#EnableMBeanExport
public class AppConfig {
}
And your managed bean should look like this :
#Component("myManagedBean")
#ManagedResource(objectName="your.domain.jmx:name=MyMBean",
description="My MBean goal")
public class AnnotationTestBean {
private int age;
#ManagedAttribute(description="The age attribute", currencyTimeLimit=15)
public int getAge() {
return age;
}
#ManagedOperation(description = "Check permissions for the given activity")
#ManagedOperationParameters( {
#ManagedOperationParameter(name = "activity",
description = "The activity to check")
})
public boolean isAllowedTo(final String activity) {
// impl
}
}
Remember to not implements an MBean interface, which would be a StandardMBean, and SelfNaming interface, which would bypass Spring naming management !
You can use KeyNamingStrategy to define all JMX-related properties inside XML configuration without adding any compile-time dependencies to Spring into the source code of your MBean:
<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.KeyNamingStrategy">
<property name="mappings">
<props>
<prop key="someSpringBean">desired.packageName:name=desiredBeanName</prop>
</props>
</property>
</bean>
If you can live with somewhat arbitrary object names, then you can use the IdentityNamingStrategy as a naming strategy for MBeanExporter and minimize the XML configuration event further:
<bean class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
<property name="namingStrategy" ref="namingStrategy"/>
</bean>
<bean id="namingStrategy"
class="org.springframework.jmx.export.naming.IdentityNamingStrategy"/>
Check spring documentation: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jmx.html section 22.3.2 explains the JDK 5.0 annotations that are available.
Section 22.4 explains mechanisms available for object naming.

Get http session / request in hibernate interceptor

I want to to implement an audit logging module to my existing system and I want to save actual logged user information and it's only the httpSession.
I'm using hibernate/spring/struts 2, and in order to get actual logged user information and call saveLog service I need the ServletContext to find those service bean or get the httpServletRequest...
I have been searching and seems binding the session to ThreadLocal usign Filter is the only way? something like this or this (last answer)
is there other suggestion? is this a commun pattern or a good practice ?
Spring can bind current request to the thread out of the box. If you use DispatcherServlet, it's done automatically, otherwise you need to declare RequestContextFilter.
Then you can access request properties via RequestContextHolder.
There was this article how to implement the Hibernate Listener as Spring bean to have full access to the Spring Context.
This may open complete new ways in your scenario (Depending on your app).
Depending on your version of Hibernate, you may be able to use Envers to accommodate fine-grained audit logging. This includes the ability to add the 'current user' from a session variable into the given Revision:
#Entity
#RevisionEntity(ExampleListener.class)
public class ExampleRevEntity extends DefaultRevisionEntity {
private String username;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
}
This integrates with Hibernate nicely through a series of eventListeners, which you can call out in Spring like so:
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
<property name="eventListeners">
<map>
<entry key="post-insert" value-ref="enversEventListener"/>
<entry key="post-update" value-ref="enversEventListener"/>
<entry key="post-delete" value-ref="enversEventListener"/>
<entry key="pre-collection-update" value-ref="enversEventListener"/>
<entry key="pre-collection-remove" value-ref="enversEventListener"/>
<entry key="post-collection-recreate" value-ref="enversEventListener"/>
</map>
</property>
</bean>
Then you can query for audit revisions through the Envers query api.
After using this on a couple of recent projects, it's my preferred audit technique when using Hibernate.
To answer your question, you can then setup a Hibernate Interceptor or Envers RevisionListener to access the 'current user' by looking it up from the current Spring context:
applicationContext.getBean("currentUser", User.class);
as long as your user is setup as a scoped bean in Spring.
Example for spring boot (spring-boot-starter 1.2.4.RELEASE).
In some controller:
#RequestMapping("login")
public UserBean login(#RequestParam("email") String email,
#RequestParam("password") String password,
HttpSession session) {
// getting usser
session.setAttribute("currentUser", user);
return user;
}
Register hibernate listeners
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.*;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.jpa.HibernateEntityManagerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;
#Component
public class UiDateListener implements PostLoadEventListener, PreUpdateEventListener {
#Inject EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
HibernateEntityManagerFactory hibernateEntityManagerFactory = (HibernateEntityManagerFactory) this.entityManagerFactory;
SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) hibernateEntityManagerFactory.getSessionFactory();
EventListenerRegistry registry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
registry.appendListeners(EventType.POST_LOAD, this);
registry.appendListeners(EventType.PRE_UPDATE, this);
}
#Override
public void onPostLoad(PostLoadEvent event) {
final Object entity = event.getEntity();
if (entity == null) return;
final UserBean currentUser = (UserBean) RequestContextHolder.currentRequestAttributes().getAttribute("currentUser", RequestAttributes.SCOPE_SESSION);
// some logic after entity loaded
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
final Object entity = event.getEntity();
if (entity == null) return false;
// some logic before entity persist
return false;
}
}

Resources