Spring AbstractRoutingDataSource is caching Datasource - spring

I have a Spring MVC application deployed as Multi-Tenant (Single Instance) on Tomcat. All users login to the same application.
User belongs to a Region, and each Region has a separate Database instance.
We are using Dynamic DataSource Routing using Spring AbstractRoutingDataSource".
This works correctly only for the first time, when User_1 from Region_1 logs into the application, Datasource_1 is correctly assigned.
But subsequently when User_2 from Reqion_2 logs into the application, AbstractRoutingDataSource never gets called and Datasource_1 gets assigned.
It looks like Spring AbstractRoutingDataSource is caching the Datasouce.
Is there a way to change this behaviour of AbstractRoutingDataSource and get it working correctly?

You should provide more details for a better understanding.
I think the problem might be related to changing the tenant identifier. You may have a ThreadLocal storage to store the tenant identifier.
public class ThreadLocalStorage {
private static ThreadLocal<String> tenant = new ThreadLocal<>();
public static void setTenantName(String tenantName) {
tenant.set(tenantName);
}
public static String getTenantName() {
return tenant.get();
}
}
AbstractRoutingDataSource should use this to retrieve the tenantId
public class TenantAwareRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return ThreadLocalStorage.getTenantName();
}
}
And you should set the tenantId on each request for the current thread that is handling the request so that the following operations will be done on the correct database. For you may add a Spring Security filter to extract the tenant identifier from JWT token or from host subdomain.

Related

Access other service's API

In our Angular + Spring boot application application, we have 2 Controllers (2 Services are internally referenced). In first controller, We are sending a File from UI and reading the content of the file , query an external application and retrieve a set of data and return only a sub-set of Data, for entering as recommendation for UI fields. why we are returning only sub-set of data received from the external application? Because, we need only those sub-set data for showing recommendations in UI.
Once the rest of the fields are filled, then, we call another controller to generate a report. But, for generation of files, the second service requires the rest of the data from external application, which is received by the first service. I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application. I also like to avoid calling the external application again to retrieve the same data again in the second service. My question is how to fetch the data received by the first service in the second service?
For example:
First controller (ExternalApplicationController), which delegates loading of loading/importing of data from files
public class Department{
private Metadata metadata; // contains data such as name, id, location, etc.,
private Collection<Employee> employees; // the list of employees working in the department.
}
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files) {
return this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files)).getMetadata();
}
}
The first service (ExternalApplicationImportService), which delegates the request to the external application for loading of department data.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
public Department loadDepartmentDetails(File file){
return app.loadDepartmentDetails(file);
}
}
The Metadata from the ExternalApplicationController is used to populated UI fields and after doing some operations (filling up some data), user requests to generate a report(which contains details from the employees of that department)
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(){
generationService.generateAnnualReports();
}
}
#Service
public class ReportGenerationService{
public void generateAnnualReports(){
//here I need access to the data loaded in the ExternalApplicationImportService.
}
}
So, I would like to access the data loaded in the ExternalApplicationImportService in the ReportGenerationService.
I also see that there would be more services created in the future and might need to access the data loaded in the ExternalApplicationImportService.
How can this be designed and achieved?
I feel that I'm missing something how to have a linking between these services, for a given user session.
Thanks,
Paul
You speak about user session. Maybe you could inject the session of your user directly in your controllers and "play" with it?
Just adding HttpSession as parameter of your controllers' methods and spring will inject it for you. Then you just have to put your data in the session during the first WS call. And recover it from the session at the second WS call.
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
generationService.generateAnnualReports();
}
}
Alternatively for the second call you could use:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(#SessionAttribute("<name of your session attribute>") Object yourdata){
generationService.generateAnnualReports();
}
}
You are starting from a wrong assumption:
I understand that Autowiring the first service in the second service, will create new instance of the first service and I will not get the first service instance, which is used to query the external application.
That is not correct: by default, Spring will create your bean as singleton, a single bean definition to a single object instance for each Spring IoC container.
As a consequence, every bean in which you inject ExternalApplicationImportService will receive the same instance.
To solve your problem, you only need a place in where temporarily store the results of your external app calls.
You have several options for that:
As you are receiving the same bean, you can preserve same state in instance fields of ExternalApplicationImportService.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
// Maintain state in instance fields
private Department deparment;
public Department loadDepartmentDetails(File file){
if (department == null) {
department = app.loadDepartmentDetails(file);
}
return department;
}
}
Better, you can use some cache mechanism, the Spring builtin is excellent, and return the cached result. You can choose the information that will be used as the key of the cached data, probably some attribute related to your user in this case.
#Service
public class ExternalApplicationImportService{
private final ExternalApp app;
#Cacheable("department")
public Department loadDepartmentDetails(File file){
// will only be invoked if the file argument changes
return app.loadDepartmentDetails(file);
}
}
You can store the information returned from the external app in an intermediate information system like Redis, if available, or even in the application underlying database.
As suggested by Mohicane, in the Web tier, you can use the http sessions to store the attributes you need to, directly as a result of the operations performed by your controllers, or even try using Spring session scoped beans. For example:
#RestController
#RequestMapping("/externalApp")
public class ExternalApplicationController{
#Autowired
private ExternalApplicationImportService importService;
#PostMapping("/importDepartmentDataFromFiles")
public Metadata importDepartmentDataFromFiles(#RequestParam("files") final MultipartFile[] files, HttpSession session) {
Deparment department = this.importService.loadDepartmentDetails(FileUtils.getInstance().convertToFiles(files));
session.setAttribute("department", department);
return deparment.getMetadata();
}
}
And:
#RestController
#RequestMapping("/reportGenerator")
public class ReportController{
#Autowired
private ReportGenerationService generationService;
#PostMapping("/generateAnnualReports")
public void generateAnnualReports(HttpSession session){
Department department = (Department)session.setAttribute("department");
// Probably you need pass that information to you service
// TODO Handle the case in which the information is not present in the session
generationService.generateAnnualReports(department);
}
}
In my opinion, the second of the proposed approaches is the best one but all are valid mechanisms to share your data between the two operations.
my recommendation for you will be to revisit your design of classes and build a proper relationship between them. I feel you need to introduce the extra logic to manage your temporal data for report generation.
#Mohicane suggested to use HTTP Session in above answer. It might be a possible solution, but it has an issue if your service needs to be distributed in the future (e.g. more than one runnable instance will serve your WEB app).
I strongly advise:
creating a separate service to manage Metadata loading process, where you will have load(key) method
you need to determine by yourself what is going to be a key
both of your other services will utilize it
this service with method load(key) can be marked by #Cacheable annotation
configure your cache implementation. As a simple one you can use In-Memory, if a question becomes to scale your back-end app, you can easily switch it to Redis/DynamoDB or other data storages.
Referances:
Spring Caching
Spring Caching Guide

Configuring Spring transactions support for MongoDB

I am using spring-boot-starter-data-mongodb:2.2.1.RELEASE and trying to add transaction support for Mongo DB operations.
I have the account service below, where documents are inserted into two collections accounts and profiles. In case an error occurs while inserting into profile collection, the insertion into accounts should rollback. I have configured Spring transactions using MongoTransactionManager.
#Service
public class AccountService {
#Transactional
public void register(UserAccount userAccount) {
userAccount = accountRepository.save(userAccount);
UserProfile userProfile = new UserProfile(userAccountDoc.getId());
userProfile = profileRepository.save(userProfile);
}
}
Enabled Spring transaction support for MongoDB.
#Configuration
public abstract class MongoConfig extends AbstractMongoConfiguration {
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
As per Spring reference doc https://docs.spring.io/spring-data/mongodb/docs/2.2.1.RELEASE/reference/html/#mongo.transactions that is all required to enable transactions for MongoDB. But this is not working. Insertions into accounts collection are not rolled back in case error occurs while inserting into profiles collection. Any suggestions if I am missing anything?
I would use command monitoring or examine query logs on the server side to ensure that:
session ids are sent with queries/write commands
transaction operations are performed (there is no startTransaction command but there is a commitTransaction)

Common shared data objects for entire application

I have some data objects that are common across a Spring boot application - one is the logged in employee object and other is a category. I have created a #Component class which contains these are static variables. This way I do not even have to autowire them. They can be used directly like CurrentContext.employee in controllers.
#Component
public final class CurrentContext {
public static Category currentCategory;
public static Employee employee;
#Autowired
private CategoryService categoryService;
#Autowired
private EmployeeService employeeService;
#EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
currentCategory = categoryService.getCategory();
}
#EventListener
public void onLoginSuccess(InteractiveAuthenticationSuccessEvent event) {
employee = employeeService.getEmployeeByUserId(((MyUserDetails) event.getAuthentication().getPrincipal()).getUserId());
}
}
Is this a right way? Please suggest if there is a better way to handle shared data
Edit
Some background - I require the current logged in employee and a category which is common for all employees. So I autowired employeeService and categoryService in my controllers and use them to get the data. They are required in almost all my controller methods, so, I wanted to create a bean of these so that I directly use them in my controller and also save frequent database calls.
Normally, we only put the dependencies related to the cross-cutting concerns (i.e dependencies that are across the whole application such as security , logging , transaction stuff , time provider etc.) in the static field.
By accessing these kind of dependencies in the static way , we don't need to pass them through method parameters /constructors from object to object , which will make the API much cleaner without such noise (BTW. This is called Ambient Context Pattern in the .NET world).
Your Employee object most probably belong to this type , so it is ok to access it in a static way. But as their scope is per session , you cannot simply put it in the static field of a class. If yes, then you always get the same employee for all sessions. Instead, you have to somehow store it in an object which is session scope (e.g HttpSession) . Then at the beginning of handling a web request , you get it from the session and then put it in a ThreadLocal which is encapsulated inside a "ContextHolder" object. You then access that "ContextHolder" in a static way.
Sound very complicated and scary ? Don't worry as Spring Security has already implemented this stuff for you. What you need to do is to customize Authentication#getPrincipal()or extend default Authentication to contain your Employee. Then get it using SecurityContextHolder.getContext().getAuthentication()
For your currentCategory , if they are not the cross-cutting concerns and is the application scope , make a singleton bean to get it values is a much better OOP design.
#Component
public final class CurrentCategoryProvider {
#Autowired
private CategoryService categoryService;
public Category getCurrentCategory(){
//or cache the value to the an internal properties depending on your requirements
return categoryService.getCategory();
}
}
You then inject CurrentCategoryProvider to the bean that need to access currentCategory.

How can we achieve multi-tenant option in the Spring scheduler?

we have implemented multi-tenant option in our application. Each tenant have each separate DB. using application filter i can manage or assign the each tenant from the request. same how can we do it in the spring boot scheduler?
#component
public class scheduler{
#Scheduled(fixedRate = 5000)
public void reminderEmail() {
//how can we fetch the exact data from exact tenant DB?
//since there is no request how can we get the tenant name for
fetching exact tenant db?
}
}
Please let me know how can we achieve this?
Something like:
...
public class TenantContext {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setTenantId(String tenantId) {
CONTEXT.set(tenantId);
}
public static String getTenantId() {
return CONTEXT.get();
}
...
}
then your Filter or Spring MVC interceptor could do this just before chaining the request:
String tenantId = request.getHeader(TENANT_HEADER_NAME);
TenantContext.setTenantId(tenantId);
and reset it on the way back:
TenantContext.setTenantId(null);
To use it in a thread not related to an http request you could just do:
TenantContext.setTenantId("tenant_1");
More could be found in my blog post Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres
If you are using a multitenant setup similar to the one at this link: https://www.ricston.com/blog/multitenancy-jpa-spring-hibernate-part-1/ and/or you have a default tenant. The easiest way to accomplish this is to add a static method to your CurrentTenantIdentifierResolverImpl class that changes the default tenant for asynchronous tasks that have no session. This is because that the scheduled task will always use the default tenant.
CurrentTenantIdentifierResolverImpl.java
private static String DEFAULT_TENANTID = "tenantId1";
public static void setDefaultTenantForScheduledTasks(String tenant) {
DEFAULT_TENANT = tenant;
}
ScheduledTask.java
#Scheduled(fixedRate=20000)
public void runTasks() {
CurrentTenantIdentifierResolverImpl.setDefaultTenantForScheduledTasks("tenantId2");
//do something
CurrentTenantIdentifierResolverImpl.setDefaultTenantForScheduledTasks("tenantId1");
}
Then after the scheduled task is complete change it back. That is how we accomplished it and it works for our needs.
If you're using a request to determine which tenant is currently active and using tenant to determine database connections, then it's impossible to do anything involving the database from a scheduled task since the scheduled task has no tenant id

Grails 2.2.3 + Spring security core + Multi-tenant Single DB plugin

I am new to grails and I am trying to implement spring security core and Multi-tenant single db plugin.
I have implemented the spring security core first and implemented custom AuthenticationProvider and Authentication.
Then I have installed the multi-tenant single db plugin and ran the 'mt-spring-security' script that automatically created custom tenantResolver and tenantRepository. I have hard-coded the tenantId in tenantResolver for testing purpose.
I have added the #MultiTenant annotation in the domain classes.
#MultiTenant
class ClientUser implements Serializable {
long idclient_user
Userprofile user
Client client
int tenantId
...
}
In the AuthenticationProvider, the ClientUser data is not filtered for the current tenant. It is bringing the data the all the tenant.
class ClientAuthenticationProvider implements AuthenticationProvider {
Authentication authenticate(Authentication auth) throws AuthenticationException {
ClientAuthentication authentication = auth
String password = authentication.credentials
String username = authentication.name
String clientName = authentication.clientName
...
Userprofile.withTransaction { status ->
def user = Userprofile.findWhere(username: username)
def client = Client.findWhere(clientname: clientName)
def clientUser = ClientUser.findWhere(client: client, user: user) <-- NOT FILTERED FOR THE CURRENT TENANT. I HARD-CODED INVALID TENANTID IN THE TENANTRESOLVER AND EXPECTING IT TO FAIL BUT IT STILL FINDS THE USER.
if (!clientUser) {
throw new UsernameNotFoundException('User not found', username)
}
...
}
...
result
}
I am not sure how the multi-tenant and spring security works together. I am having a hard time understanding the Architecture/design.
If anyone could provided me with a sample implementation or point me in the right direction, it will be really helpful.
Thanks,
dinesh
The problem was that the multitenant filter was registered before the spring security filter so the tenantResolver was not called until after the Spring security authentication. I fixed this problem by setting the resolveTenantBeforeLogin to true in the config.groovy
In config.groovy, add this line
multiTenant.resolveTenantBeforeLogin=true
After i added this line, the tenantResolver is called first and then the authentication.

Resources