Spring Boot JPA: How to avoid creating multiple repositories for identical databases? - database

My spring boot mvc project interacts with a database via a repository interface, which works nicely using Spring boot default configurations:
spring:
datasource:
url: jdbc:mysql://localhost/some_schema
username:
...
#Configuration
#EnableJpaRepositories(basePackages = {"my.path.to.repository"})
public class Application extends WebMvcConfigurerAdapter {
....
Now depending on some runtime condition, I need to interact with an identical second database (same schema) in a separate location. The solutions I found all point to creating a separate repository package per datasource.
Since the databases are identical, however, is there an elegant way to avoid duplicating the repository package for each added datasource?

You can accomplish this with a Spring AbstractRoutingDataSource.
Roughly:
public class ChooseOneDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
if (***some runtime condition***) {
return "dataSource1";
} else {
return "dataSource2";
}
}
}
And in your conguration:
#Bean
#ConfigurationProperties(prefix = "dataSource1")
DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "dataSource2")
DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
#Bean
DataSource dataSource() {
AbstractRoutingDataSource dataSource = new ChooseOneDataSource();
Map<Object,Object> resolvedDataSources = new HashMap<>();
resolvedDataSource.put("dataSource1", dataSource1());
resolvedDataSource.put("dataSource2", dataSource2());
dataSource.setDefaultTargetDataSource(dataSource1()); // << default
dataSource.setTargetDataSources(resolvedDataSources);
return dataSource;
}
For more info/examples:
http://fizzylogic.nl/2016/01/24/Make-your-Spring-boot-application-multi-tenant-aware-in-2-steps/
https://spring.io/blog/2007/01/23/dynamic-datasource-routing/

Related

abp io AbpDataFilterOptions

i am trying to configure new data filter for IMayHaveCreator. I saw example for ISoftDelete and did the same thing.
in MyAppEntityFrameworkCoreModule ive added another configure method for Filter but it does not work
public class SimplyAirEntityFrameworkCoreModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
SimplyAirEfCoreEntityExtensionMappings.Configure();
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAbpDbContext<SimplyAirDbContext>(options =>
{
/* Remove "includeAllEntities: true" to create
* default repositories only for aggregate roots */
options.AddDefaultRepositories(includeAllEntities: true);
});
Configure<AbpDbContextOptions>(options =>
{
/* The main point to change your DBMS.
* See also SimplyAirMigrationsDbContextFactory for EF Core tooling. */
options.UseNpgsql();
});
Configure<AbpDataFilterOptions>(options =>
{
options.DefaultStates[typeof(IMayHaveCreator)] = new DataFilterState(isEnabled: true);
});
}
}
am i doing something wrong
I've managed to implement it. solution was to add override for CreateFilterExpression and ShouldFilterEntity methods in dbContext for that interface

About load supported cultures from DB in .NET CORE

I have a Language entity with all supported languages in my db, each language has a culture string attribute. I want to load supported cultures from DB.
In my service initializer I have it:
public void ConfigureServices(IServiceCollection services)
{
// ... previous configuration not shown
services.Configure<RequestLocalizationOptions>(
opts =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-GB"),
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("fr-FR"),
new CultureInfo("fr"),
};
opts.DefaultRequestCulture = new RequestCulture("en-GB");
// Formatting numbers, dates, etc.
opts.SupportedCultures = supportedCultures;
// UI strings that we have localized.
opts.SupportedUICultures = supportedCultures;
});
}
How I can access my DB context inside it?
There is any other better way to do it?
I don't think there's an out of the box solution for this.
However, you can implement your own middleware that achieves this by using ASP.Net's RequestLocalizationMiddleware:
public class CustomRequestLocalizationMiddleware
{
private readonly RequestDelegate next;
private readonly ILoggerFactory loggerFactory;
public CustomRequestLocalizationMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
this.next = next;
this.loggerFactory = loggerFactory;
}
public async Task Invoke(HttpContext context /* You can inject services here, such as DbContext or IDbConnection*/)
{
// You can search your database for your supported and/or default languages here
// This query will execute for all requests, so consider using caching
var cultures = await Task.FromResult(new[] { "en" });
var defaultCulture = await Task.FromResult("en");
// You can configure the options here as you would do by calling services.Configure<RequestLocalizationOptions>()
var options = new RequestLocalizationOptions()
.AddSupportedCultures(cultures)
.AddSupportedUICultures(cultures)
.SetDefaultCulture(defaultCulture);
// Finally, we instantiate ASP.Net's default RequestLocalizationMiddleware and call it
var defaultImplementation = new RequestLocalizationMiddleware(next, Options.Create(options), loggerFactory);
await defaultImplementation.Invoke(context);
}
}
Then, we inject the required services and use the custom middleware in Startup.cs or Program.cs as follows:
services.AddLocalization()
/* ... */
app.UseMiddleware<CustomRequestLocalizationMiddleware>()
Do not call app.UseRequestLocalization(), because this would call ASP.Net's RequestLocalizationMiddleware again with the default options, and override the culture that has been resolved previously.

Identity Server 4 AddOidcStateDataFormatterCache does not apply to AddGoogle

When using the AddOidcStateDataFormatterCache method via:
services.AddOidcStateDataFormatterCache();
It only applies to providers which are added using
.AddOpenIdConnect();
Is there a way to apply the distributedCacheFormatter when using
.AddGoogle()
Google is also an OpenId Provider and can be added using .AddOpenIdConnect or .AddGoogle, but using .AddGoogle doesn't use the state data formatter. I confirmed this by checking the redis cache (used as the underlying implementation of IDistributedCache) and saw a key created "DistributedCacheStateDataFormatter..." when using .AddOpenIdConnect, but nothing is created when using .AddGoogle.
I'm thinking this might be because .AddGoogle might use a different authentication handler which doesn't get picked up automatically by AddOidcStateDataFormatterCache
This is because the GoogleOptions class inherits from OAuthOptions and not OpenIdConnectOptions but they both have a ISecureDataFormat<AuthenticationProperties> StateDataFormat so you could re-use the DistributedCacheStateDataFormatter provided by identityserver4
The post-configure class:
internal class ConfigureGoogleOptions : IPostConfigureOptions<GoogleOptions>
{
private string[] _schemes;
private readonly IHttpContextAccessor _httpContextAccessor;
public ConfigureGoogleOptions(string[] schemes, IHttpContextAccessor httpContextAccessor)
{
_schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
public void PostConfigure(string name, GoogleOptions options)
{
// no schemes means configure them all
if (_schemes.Length == 0 || _schemes.Contains(name))
{
options.StateDataFormat = new DistributedCacheStateDataFormatter(_httpContextAccessor, name);
}
}
}
And the registration helper (add this to your own static class):
public static IServiceCollection AddGoogleStateDataFormatterCache(this IServiceCollection services, params string[] schemes)
{
services.AddSingleton<IPostConfigureOptions<GoogleOptions>>(
svcs => new ConfigureGoogleOptions(
schemes,
svcs.GetRequiredService<IHttpContextAccessor>())
);
return services;
}

Spring-boot application not finding index.html

I am wiring a AngularJS and spring-boot application together by hand for the first time. The issues I am running into is my #RestController is not returning the index page:
#RestController
public class IndexController {
#RequestMapping("/")
public String index(){
System.out.println("Looking in the index controller.........");
return "index";
}
}
Directory:
It keeps rendering the default 404 error page:
----------------UPDATE 1------------------
I have added a configuration file:
#Configuration
public class IndexPageConfiguration {
#Bean
public InternalResourceViewResolver viewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/app/");
resolver.setSuffix(".html");
return resolver;
}
}
RestController
#RestController
public class IndexController {
#RequestMapping("/")
public String index(){
System.out.println("Looking in the index controller.........");
return "index";
}
}
main class:
#SpringBootApplication(scanBasePackages = { "com.serviceImpl","com.service","com.config" },exclude = { ErrorMvcAutoConfiguration.class })
public class SpringCrudApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCrudApplication.class, args);
}
}
The above main class is still returning the default 404 error page.
On the other hand, Spring will automatically look for the index.html page if you put it directly under webapp folder. So you don't need any configuration.
This is just another way to do it.
You need to configure InternalRosourceViewResolver to let the spring know your jsp location
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/app/");
resolver.setSuffix(".html");
return resolver;
}
So Spring will append and append location and suffix to your View returned.
I think it is good idea to keep your views separately in any other folder and configure your folder location according to it.
If you want to continue with your current set up
you should return "/app/index.html" from your controller.
Spring boot provides White label error page to hide your stack trace when a Server side error/ exception occurs, this will help us from protecting our code from intruders.
If you want to get rid of white label error.
In your #SpringBootApplication specify excludes ErrorMvcAutoConfiguration.class
#SpringBootApplication(scanBasePackages = { "com.ekart.app" }, exclude = { ErrorMvcAutoConfiguration.class })
If you are not using #SpringBootApplication annotatio, you should supply same same excludes in #EnableAutoConfiguration annotation

How to configure Spring and Angular to work together

I have a spring REST server (v3.2) and AngularJS for the client code.
From my understanding in the basic scenario the user navigates to the base domain .com, index.html is being sent back and
and from that point Angular manages the communication.
My questions are:
1. How to set Spring to return the Angular file.
2. How to handle a situation where the user does not go though the base domain and just navigates to
.com/books/moby-dick which currently returns a JSON representation of the Moby-Dick book that was suppose
to be rendered by the client
A good tutorial will be highly appreciated.
This is my web initialzer class:
public class WebAppInitializer implements WebApplicationInitializer {
private static Logger LOG = LoggerFactory.getLogger(WebAppInitializer.class);
#Override
public void onStartup(ServletContext servletContext) {
WebApplicationContext rootContext = createRootContext(servletContext);
configureSpringMvc(servletContext, rootContext);
FilterRegistration.Dynamic corsFilter = servletContext.addFilter("corsFilter", CORSFilter.class);
corsFilter.addMappingForUrlPatterns(null, false, "/*");
// configureSpringSecurity(servletContext, rootContext);
}
private WebApplicationContext createRootContext(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
// rootContext.register(CoreConfig.class, SecurityConfig.class);
rootContext.register(CoreConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.setInitParameter("defaultHtmlEscape", "true");
return rootContext;
}
private void configureSpringMvc(ServletContext servletContext, WebApplicationContext rootContext) {
AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
mvcContext.register(MVCConfig.class);
mvcContext.setParent(rootContext);
ServletRegistration.Dynamic appServlet = servletContext.addServlet(
"webservice", new DispatcherServlet(mvcContext));
appServlet.setLoadOnStartup(1);
Set<String> mappingConflicts = appServlet.addMapping("/");
if (!mappingConflicts.isEmpty()) {
for (String s : mappingConflicts) {
LOG.error("Mapping conflict: " + s);
}
throw new IllegalStateException(
"'webservice' cannot be mapped to '/'");
}
}
This is my MVC configuration file:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.yadazing.rest.controller"})
public class MVCConfig extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
(disclaimer: I am the author of JHipster)
You can have a look at JHipster which will generate such an application for you, with a Spring backend and an AngularJS frontend.
As the generator goes far beyond what you need (security, etc), you can also have a look at our sample application.
How about this then for #1:
registry.addResourceHandler("/index.html").addResourceLocations("/index.html");

Resources