Spring rest api - Get method problem with dots - java

I have problem when trying to create get method which allows dots as a parameter.
#GetMapping(path = "test/{id:.+}")
#ResponseBody
public String getTest(#PathVariable String id) {
return id;
}
So it works for example for path
test/core.txt
But it does not for
test/core.something
I got error like
{
"timestamp": 1623837131322,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.NoClassDefFoundError",
"message": "Could not initialize class org.springframework.web.accept.PathExtensionContentNegotiationStrategy$ActivationMediaTypeFactory",
"path": "/api/v1/test/core.somethign"
}
java.lang.NoClassDefFoundError: Could not initialize class org.springframework.web.accept.PathExtensionContentNegotiationStrategy$ActivationMediaTypeFactory ...
Also tried to extend WebMvcConfigurationSupport with
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
I am using spring boot version 1.5.22.RELEASE.
Do you have any idea what can be wrong, seems like my configuration is ignored somehow
package eu.test;
import org.springframework.context.annotation.*;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
#Configuration
#ComponentScan
#PropertySource(
ignoreResourceNotFound = false,
value = "classpath:application.properties")
public class ApplicationConfiguration extends WebMvcConfigurationSupport {
#Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}

Add below bean in your appconfig file.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
#Configuration
public class MvcConfig extends WebMvcConfigurationSupport{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping handlerMapping = super.requestMappingHandlerMapping();
handlerMapping.setUseSuffixPatternMatch(false);
handlerMapping.setUseTrailingSlashMatch(false);
return handlerMapping;
}
}
Update : This class is deprecated. Now you have to use below code. I tested it and i'm posting here.
#Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {
#Override
protected void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
Output:
test/abc.tera : abc.tera
test/core.something : core.something

Related

Understanding Primary annotation in Profile

I am trying to understand the behaviour of #Primary in #Profile from this video
Dependency Injection using profile.
The active profile in file application.properties is english and running it gives error
expected single matching bean but found 2: helloWorldServiceEnglish,helloWorldServiceSpanish
Adding #Primary annotation in helloConfig.java resolves the error:
#Bean
#Profile("english")
#Primary
public HelloWorldService helloWorldServiceEnglish(HelloWorldFactory factory) {
return factory.createHelloWorldService("en");
}
When I am Autowiring using Profile and there is only one single Profile named english then why it is searching for other beans which do not have #Profile annotation? And how adding #Primary is changing this behaviour?
Does Spring internally first scans for Autowire by type and completely ignore #Profile because of which it throws error expected single matching bean but found 2.
helloConfig.java
package com.spring.config;
import com.spring.services.HelloWorldFactory;
import com.spring.services.HelloWorldService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
#Configuration
public class HelloConfig {
#Bean
public HelloWorldFactory helloWorldFactory() {
return new HelloWorldFactory();
}
#Bean
#Profile("english")
#Primary
public HelloWorldService helloWorldServiceEnglish(HelloWorldFactory factory) {
return factory.createHelloWorldService("en");
}
#Bean
#Qualifier("spanish")
public HelloWorldService helloWorldServiceSpanish(HelloWorldFactory factory) {
return factory.createHelloWorldService("es");
}
#Bean
#Profile("french")
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
#Profile("german")
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
#Profile("polish")
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
#Profile("russian")
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
}
DependencyInjectionApplication.java
package com.spring.componentScan;
import com.spring.controllers.GreetingController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan("com.spring")
public class DependencyInjectionApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(DependencyInjectionApplication.class, args);
GreetingController controller = (GreetingController) ctx.getBean("greetingController");
controller.sayHello();
}
}
GreetingController.java
package com.spring.controllers;
import com.spring.services.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
#Controller
public class GreetingController {
private HelloWorldService helloWorldService;
private HelloWorldService helloWorldServiceSpanish;
#Autowired
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
#Autowired
#Qualifier("spanish")
public void setHelloWorldServiceFrench(HelloWorldService helloWorldServiceSpanish) {
this.helloWorldServiceSpanish = helloWorldServiceSpanish;
}
public String sayHello() {
String greeting = helloWorldService.getGreeting();
System.out.println(helloWorldServiceSpanish.getGreeting());
System.out.println(greeting);
return greeting;
}
}
Application.properties
spring.profiles.active=english
Complete Source code:
If you consider this source code
#Bean(name = "french")
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
here is no #Profile annotation, and thats why Spring creating multiple beans of same type, if you want them to recognized differently, try giving them well qualified explicit name by #Bean(name="polish") (or Spring anyway assign them by looking at #Bean method name) and then autowire using #Qualifier("polish")
Okay, so your example is not updated with the latest code. But I assume that you want to create multiple instances of the same bean type and use them for different languages. It's easy to achieve and you don't need to have #Profile and #Primary for that.
What you need is just assign qualifier for the bean instance (or use the one that spring assigns by default). And the inject bean by this qualifier.
#Bean
public HelloWorldService helloWorldServiceFrench(HelloWorldFactory factory) {
return factory.createHelloWorldService("fr");
}
#Bean
public HelloWorldService helloWorldServiceGerman(HelloWorldFactory factory) {
return factory.createHelloWorldService("de");
}
#Bean
public HelloWorldService helloWorldServicePolish(HelloWorldFactory factory) {
return factory.createHelloWorldService("pl");
}
#Bean
public HelloWorldService helloWorldServiceRussian(HelloWorldFactory factory) {
return factory.createHelloWorldService("ru");
}
Controller:
#Controller
public class GreetingController {
#Qualifier("helloWorldServiceGerman")
#Autowired
private HelloWorldService helloWorldServiceGerman;
#Qualifier("helloWorldServiceFrench")
#Autowired
private HelloWorldService helloWorldServiceFrench;
#Qualifier("helloWorldServicePolish")
#Autowired
private HelloWorldService helloWorldServicePolish;
#Qualifier("helloWorldServiceRussian")
#Autowired
private HelloWorldService helloWorldServiceRussian;
. . .
}
Update
You usually mark a bean as #Primary when you want to have one bean instance as a priority option when there are multiple injection candidates. Official doc with good example.
#Profile just narrows bean search, but still if you have multiple beans of the same type in the same profile - #Primary to the rescue (if you autowire by type, autowire by qualifier still works fine though).
Developers usually do this to avoid NoUniqueBeanDefinitionException that you had initially.

Spring Boot - Test Rest Api With Custom PreAuthorize Expression and Authentication

I looking for a way how to test Spring Boot REST API with following setup:
#RestController
class SomeRestController {
#Autowired
private SomeService someService;
#GetMapping("/getSome")
#PreAuthorize("#canGetSome.isValid()")
public SomeObject getSomeObject() {
return someService.getSomeObject();
}
}
_
#Component
public class CanGetSome{
#Autowired
private final LoggedUser loggedUser;
public boolean isValid() {
return loggedUser.getPermissions().isCanGetSome();
}
}
_
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
...
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionFixation().newSession()
.and()
.authorizeRequests())
.anyRequest().authenticated();
}
//custom LoggedUser object which is initzialized on authentication sucessfull
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public LoggedUser loggedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (LoggedUser) authentication.getPrincipal();
}
...
}
My test case:
#SpringBootTest(
classes = SpringBootApplication,
webEnvironment = RANDOM_PORT)
#ContextConfiguration
class RestSecurityTest extends Specification {
#Autowired
private TestRestTemplate testRestTemplate
#LocalServerPort
private int port
#WithCustomMockUser
def "test testRestTemplare"(){
expect:
def respone = testRestTemplate.getForObject('http://localhost:'+ port+'/getSome', String)
}
_
public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {
#Override
public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
//init securityContext like #WithMockUser but with LoggedUser as principal which return true on loggedUser.getPermissions().isCanGetSome();
}
}
Unfortunately I getting following response in test:
{
"timestamp": 1524759173851,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/getSome"
}
I also debug different spring filters after request and there SecurityContext authentication is null and later is switched to AnonymousAuthenticationToken
I don't know why SecurityContext is a null after request and isn't SecurityContext which is initialized with #WithCustomMockUser annotation. Any ideas how to fix it ?
Spring MVC should be the way to go.
Test class:-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestContext.class, TestWebMvcConfigurerAdapter.class, SecurityConfiguration .class })
#WebAppConfiguration
public class SomeControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.addFilters(this.springSecurityFilterChain)
.build();
}
#Test
public void shouldPreAuthorise() throws Exception {
this.mockMvc.perform(get("/getSome")
.with(user("user.name"))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Test web configurer:-
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {
"your.package.here"
})
public class TestWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
}
Some mock objects:-
#Configuration
public class TestContext {
#Bean
public SomeService someService() {
return mock(SomeService.class);
}
}

EmbeddedServletContainerCustomizer in spring boot 2.0

I try to migrate my app from spring boot 1.5 to 2.0
The problem is that I cannot find EmbeddedServletContainerCustomizer.
Any ideas how to make it through?
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
}
Update:
I found it as ServletWebServerFactoryCustomizer in org.springframework.boot.autoconfigure.web.servlet package.
#Bean
public ServletWebServerFactoryCustomizer customizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
}
But there is an error:
Cannot resolve method
'addErrorPages(org.springframework.boot.web.server.ErrorPage)'
I also had to change import of new Error Page from org.springframework.boot.web.servlet to org.springframework.boot.web.server
I think you need ConfigurableServletWebServerFactory, instead of ServletWebServerFactoryCustomizer.
You can find the code snippet below:
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
#Configuration
public class ServerConfig {
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"));
return factory;
}
}
The above code is for Undertow. For tomcat, you need to replace UndertowServletWebServerFactory with TomcatServletWebServerFactory.
You will need to add the following dependency to your project (in case of Maven):
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>2.0.26.Final</version>
</dependency>
You don't need the container customizer to register your error pages. Simple bean definition implemented as a lambda does the trick:
#Bean
public ErrorPageRegistrar errorPageRegistrar() {
return registry -> {
registry.addErrorPages(
new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthenticated"),
);
};
}
From Spring Boot 2 on the WebServerFactoryCustomizer has replaced the EmbeddedServletContainerCustomizer:
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
return (factory) -> factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html"));
}
Alternatively you might add a view controller like
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/unauthorized").setViewName("forward:/401.html");
}
and then your WebServerFactory should point to /unauthorized instead:
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> webServerFactoryCustomizer() {
return (factory) -> factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized"));
}
You can find the code snippet below:
#Bean
public ErrorPageRegistrar errorPageRegistrar(){
return new MyErrorPageRegistrar();
}
// ...
private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
#Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}

Spring AOP blocks RestController

I try to learn Spring AOP. I've created simple spring boot project in IDEA.
Service.java
package com.example.demo.service;
//imports..
public interface Service {
public DataEntity getData();
}
ServiceImpl.java
package com.example.demo.service;
//imports..
#RestController("service")
public class ServiceImpl implements Service {
#RequestMapping(value="/test", method= RequestMethod.GET)
public DataEntity getData() {
DataEntity data = new DataEntity();
data.setData("SomeString");
return data;
}
}
ServiceCallingAspect.java
package com.example.demo.aspects;
//imports..
#Aspect
#EnableAspectJAutoProxy
#Component
public class ServiceCallingAspect {
private Log log = LogFactory.getLog(ServiceCallingAspect.class);
#AfterReturning("execution(public * com.example.demo.service.*.*(..))")
public void logBeforeRestCall(JoinPoint pjp) throws Throwable {
log.info(" POST REST call " + pjp);
}
}
DemoApplication.java
package com.example.demo;
//..
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
So when I try to call my rest service on http://localhost:8080/test, I get something like that.
{
"timestamp": 1514109432038,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/test"
}
When I disable my aspect (just comment all annotations in ServiceCallingAspect.java) the service works perfectly. Can you show me where I am wrong?
Change #EnableAspectJAutoProxy to #EnableAspectJAutoProxy(proxyTargetClass=true).
#Aspect
#EnableAspectJAutoProxy(proxyTargetClass=true)
#Component
public class ServiceCallingAspect {
.....
}

Is there a way to merge #Configurations that extend WebMvcConfigurerAdapter and WebSecurityConfigurerAdapter into one #Configuration?

I'd like to merge two configurations into one configuration. Here's how they currently look:
WebMvcConfigurerAdapter
#Configuration
#EnableWebMvc
#Import(...)
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
#Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
//...
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//...
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
//...
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
//...
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//...
}
WebSecurityConfigurerAdapter
#EnableAsync
#EnableScheduling
#EnableTransactionManagement
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(
basePackages = {"..."})
#Import({
...
})
#PropertySources({
#PropertySource("..."),
#PropertySource(
ignoreResourceNotFound = true,
value = "...")
})
#EnableWebSecurity
#Configuration
public class AdminConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//...
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//...
}
I've looked through the Spring class hierarchy and seen that these do not extend classes that are related in any way. Are there annotations that exist to replace them? Can this be solved by implementing interfaces instead of extending classes?
The reason I want to merge these is because I want to have a single config so that I don't have to think about which one to use when I'm testing my app.
I wouldn’t suggest placing them in the same class. This is not advisable for the same reason you don't cram all of your logic in a single class. Keep your configuration concise and to the point. What's more is this can really get nasty w/ circular bean references at times.
I'd recommend doing composition to solve your problem:
#Configuration
#Import({AdminConfig.class, WebApplicationConfig.class})
public class TheConfig {}
Now you can just refer to TheConfig.
Alternatively, if this only about tests you can place the configurations on a meta annotation For example:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#ContextConfiguration(classes = {..})
public #interface TheTests { }
Finally if you really want to use a single class you can do something like this:
#EnableWebMvc
#Import(...)
#EnableAsync
#EnableScheduling
#EnableTransactionManagement
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(
basePackages = {"..."})
#Import({
...
})
#PropertySources({
#PropertySource("..."),
#PropertySource(
ignoreResourceNotFound = true,
value = "...")
})
#EnableWebSecurity
#Configuration
public class TheConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {
...
}
The additional problem with this is that WebMvcConfigurerAdapter is provided so you don't need to implement all the methods in the API (only the ones you need). What's more is it shields you from changes within the interface which may add methods.
The simplest and most idiomatic approach is to make each configurer a static nested class on the top-level #Configuration class:
#Configuration
public class TestConfig {
#Configuration
#EnableWebSecurity
public class Security extends WebSecurityConfigurerAdapter {
...
}
...
}
Import the top-level class, and Spring will import the nested configurations as well.

Categories