Multiple R2DBC datasource with Spring boot - spring-boot

I want to migrate my app to WebFlux, but the tricky part that I have bean which connects to 6 data sources by such mechanism
public class MultiRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return //code which sets context for chosen db;
}
}
Then I'm creating 6 data sources which is then managed by multiRoutingDataSource
#Bean(name = "multiRoutingDataSource")
public DataSource multiRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(ident, MyDataSourceBean());
MultiRoutingDataSource multiRoutingDataSource = new MultiRoutingDataSource();
multiRoutingDataSource.setTargetDataSources(targetDataSources);
return multiRoutingDataSource;
}
and this data sources could be changed in runtime. This multiRouting then set into entity manager.
Is there something similar with WebFlux?
I found
public class MultiRoutingDataSource extends AbstractRoutingConnectionFactory {
#Override
protected Mono<Object> determineCurrentLookupKey() {
return null;
}
But how to create beans with connections and switch them in runtime like I'm doing in Spring MVC?

If you want multi R2dbc connectionfactories at the same application, check my example multi-r2dbc-connectionfactories.
For multi-tenancy support, check multi-tenancy-r2dbc.

Related

How to dynamically set the JPA/Hibernate dialect when using AbstractRoutingDataSource with different database types?

I'm using AbstractRoutingDataSource in a Spring Boot (Data JPA) application to access different databases (Mariadb, SQL Server) depending of a request parameter.
public class DataSourceRouter extends AbstractRoutingDataSource {
public DataSourceRouter(Map<Object, Object> targetDataSources, DataSource defautDataSource) {
setTargetDataSources(targetDataSources);
setDefaultTargetDataSource(defautDataSource);
}
#Override
protected Object determineCurrentLookupKey() {
return SelectedDataSourceContextHolder.getSelectedDataSource();
}
}
This datasource (routingDatasource) is configured into the LocalContainerEntityManagerFactoryBean.
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
final EntityManagerFactoryBuilder builder) {
return buildEntityManagerFactory(builder, routingDatasource());
}
protected LocalContainerEntityManagerFactoryBean buildEntityManagerFactory(final EntityManagerFactoryBuilder builder, DataSource dataSource) {
return builder.dataSource(dataSource)
.packages(SomeEntity.class.getPackageName()).properties(
Map.of("hibernate.dialect_resolvers", "xxx.DynamicDialectResolver")
)
.build();
}
The problem with this approach is that we can only configure a single hibernate dialect, which should be dynamic depending on the underlying selected datasource. In order to do so I tried setting up a hibernate.dialect_resolvers:
public class DynamicDialectResolver implements DialectResolver {
#Override
public Dialect resolveDialect(DialectResolutionInfo info) {
Dialect dialect;
switch (info.getDatabaseName()) {
case "MariaDB":
dialect = new org.hibernate.dialect.MySQL5Dialect();
break;
case "SQL Server":
dialect = new org.hibernate.dialect.SQLServer2012Dialect();
break;
default:
dialect = null;
break;
}
return dialect;
}
}
However this resolver is only invoked once, so the selected dialect with be fixed into the one it selects that first time.
Is there a way to set the dialect dynamically at runtime?
That's not possible. Hibernate generates SQL on bootstrap that is specific to the dialect which is then cached. If you need to support multiple databases, you will need multiple session factories or persistence units.

Creating SAML repository registrations dynamically in Spring Security (Spring Boot)

I have created a sample project which can demonstrate SAML 2 SSO capabilities with saml providers such as Azure AD and Okta.
I was able to configure both of above providers at once in spring configuration by using RelyingPartyRegistrationRepository and both of them are working as expected.
#Bean
protected RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
List<RelyingPartyRegistration> registrationList = new ArrayList<>();
registrationList.add(oktaRegistration);
registrationList.add(azureRegistration);
return new InMemoryRelyingPartyRegistrationRepository(registrationList);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize ->
authorize.antMatchers("/").permitAll().anyRequest().authenticated()
).saml2Login();
RelyingPartyRegistrationResolver relyingPartyRegistrationResolver = new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrations());
Saml2MetadataFilter filter = new Saml2MetadataFilter(relyingPartyRegistrationResolver, new OpenSamlMetadataResolver());
http.addFilterBefore(filter, Saml2WebSsoAuthenticationFilter.class);
return http.build();
}
I would like to know whether there is any way to create RelyingPartyRegistrationRepository dynamically once the application fully started. The requirement is to take the SAML metadata file from user in some sort of a form upload and then create RelyingPartyRegistrationRepository based on it.
The issue is, RelyingPartyRegistrationRepository is a Spring bean which is used by the Spring security internals. In this case even though we could create new RelyingPartyRegistrationRepository instances, will Spring security take them dynamically?
You will not create multiple RelyingPartyRegistrationRepository, you will create your custom implementation of RelyingPartyRegistrationRepository that accepts adding new entries to it. A simple example:
#Service
public class MyRelyingPartyRegistrationRepository implements RelyingPartyRegistrationRepository {
private final List<RelyingPartyRegistration> registrations = new ArrayList<>();
public MyRelyingPartyRegistrationRepository() {
addDefaultRegistrations();
}
private void addDefaultRegistrations() {
RelyingPartyRegistration oktaRegistration = RelyingPartyRegistrations.fromMetadataLocation("https://trial-27.okta.com/app/e/sso/saml/metadata").registrationId("okta").build();
RelyingPartyRegistration azureRegistration = RelyingPartyRegistrations.fromMetadataLocation("file:D:\\saml-test-5.xml").registrationId("azure-saml-test").build();
add(oktaRegistration);
add(azureRegistration);
}
#Override
public RelyingPartyRegistration findByRegistrationId(String registrationId) {
for (RelyingPartyRegistration registration : this.registrations) {
if (registration.getRegistrationId().equals(registrationId)) {
return registration;
}
}
return null;
}
public void add(RelyingPartyRegistration newRegistration) {
this.registrations.add(newRegistration);
}
}
And then in a Controller, for example, you can autowire this dependency and add new registrations to it:
#RestController
public class SamlController {
private final MyRelyingPartyRegistrationRepository repository;
#PostMapping("/registration")
public void addRegistration(/* receive it somehow */) {
this.repository.add(theRegistration);
}
}

Spring session jdbc - How to add multiple HttpSessionIdResolver for a single application

I have a problem in injecting multiple HttpSessionIdResolver for a single spring application.
For normal web application I would like to use CookieHttpSessionIdResolver
For Rest API I would go for HeaderHttpSessionIdResolver and Rest API url will be like "/api/**"
Internally spring sets a bean and uses that bean for all request(In this case HeaderHttpSessionIdResolver
and my web stopped working because i dont set X-Auth-Token header for every request) but i would like to override it.
Could any one please help me.
Thank you.
#EnableJdbcHttpSession(maxInactiveIntervalInSeconds = 3600)
public class SessionConfig extends AbstractHttpSessionApplicationInitializer{
#Autowired
#Qualifier("userDatabase")
private DataSource dataSource;
#Bean
public DataSource dataSource() {
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean(value = "httpSessionIdResolver")
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean(value = "cookieHttpSessionIdResolver")
public HttpSessionIdResolver cookieHttpSessionIdResolver() {
return new CookieHttpSessionIdResolver();
}
}
I overridden spring session to enable both cookie and header based session.
Now it's working fine.
Currently I'm checking for URL that contains /api/* and if it contains i'm using header based other wise cookie based session.

How to create a dynamic datasource using SpringBoot

I have a springboot application, with its own datasource (let's call DB1) set on properties working fine.
But this application needs do configure a new datasource (DB2), using some parameters the user have informed before and stored in DB1.
My idea is to create a named bean, so a specific part of my application can use to access DB2 tables. I think it is possible to do that by restarting the application, but I would like to avoid it though.
Besides, I need that some part of my code use the new datasource (spring data jpa, mappings, and so on). I don't know if this matter, but it is a web application, so I cannot create the datasource only for the request thread.
Can you help me?
Thanks in advance.
Spring has dynamic datasource routing if that's where you are headed. In my case it is the same schema (WR/RO)
public class RoutingDataSource extends AbstractRoutingDataSource {
#Autowired
private DataSourceConfig dataSourceConfig;
#Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
public enum DbType {
MASTER, WRITE, READONLY,
}
Then you need a custom annotation and an aspect
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ReadOnlyConnection {
}
#Aspect
#Component
#Order(1)
public class ReadOnlyConnectionInterceptor {
Pointcut(value = "execution(public * *(..))")
public void anyPublicMethod() {}
#Around("#annotation(readOnlyConnection)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
Object result = null;
try {
DbContextHolder.setDbType(DbType.READONLY);
result = proceedingJoinPoint.proceed();
DbContextHolder.clearDbType();
return result;
} finally {
DbContextHolder.clearDbType();
}
}
}
And then you can act on you DB with the tag #ReadOnlyConnection
#Override
#Transactional(readOnly = true)
#ReadOnlyConnection
public UnitDTO getUnitById(Long id) {
return unitRepository.findOne(id);
}
An example can be found here: https://github.com/afedulov/routing-data-source.
I used that as a basis for my work although it is still in progress because I still need to resolve runtime dependencies ( i.e. hibernate sharding ).

Spring-data-neo4j multiple graphs

I know there's some similar topic out there, but none of them gives a solution. So, if using Spring-data-neo4j, is there any way to connect to multiple graphs? NOT graphs in the same instance with different labels.
Or equivalently, I can ask this question:
How can I configure spring-data-neo4j to have multiple sessions to different Neo4j instances on different ports.
Thanks.
EDIT
Thanks to #Hunger, I think I am one step forward. Now the question is: how to confiture spring-data-neo4j to have multiple 'PereistenceContext' and each of them refers to an individual Neo4j instance.
You can configure different application contexts with different REST-API's declared pointing to different databases.
You should not mix objects or sessions from those different databases though.
So you might need qualifiers for injection.
How about having multiple configurations :
//First configuration
#Configuration
#EnableNeo4jRepositories(basePackages = "org.neo4j.example.repository.dev")
#EnableTransactionManagement
public class MyConfigurationDev extends Neo4jConfiguration {
#Bean
public Neo4jServer neo4jServer() {
return new RemoteServer("http://localhost:7474");
}
#Bean
public SessionFactory getSessionFactory() {
// with domain entity base package(s)
return new SessionFactory("org.neo4j.example.domain.dev");
}
// needed for session in view in web-applications
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
}
and another one
//Second config
#Configuration
#EnableNeo4jRepositories(basePackages = "org.neo4j.example.repository.test")
#EnableTransactionManagement
public class MyConfigurationDev extends Neo4jConfiguration {
#Bean
public Neo4jServer neo4jServer() {
return new RemoteServer("http://localhost:7475");
}
#Bean
public SessionFactory getSessionFactory() {
// with domain entity base package(s)
return new SessionFactory("org.neo4j.example.domain.test");
}
// needed for session in view in web-applications
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
}

Resources