Spring MVC Matrix Variables retrive only one param value - java

I'm building a spring-mvc rest API application and I intend to use matrix variables for some of my endpoints. Unfortunately I'm not able to retrive more than one value per matrix variable used.
My spring-mvc version is spring-webmvc:4.3.12.RELEASE
I followed the steps shown in this example of implementation : http://www.baeldung.com/spring-mvc-matrix-variables.
I've enabled Spring MVC Matrix Variables :
package fr.compagny.project.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.util.UrlPathHelper;
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
So I've created 2 test endpoints :
package fr.compagny.project.webservice;
import [...]
#Api
#RefreshScope
#RestController
#RequestMapping(value = "/my_awesome_project")
public class ProjectWS {
//Services
private ProjectService projectService;
//Validator
private ValidatorService validator;
#ApiOperation(value = "Matrix Variable Test 1.")
#GetMapping(value = "/matrix_test_one/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public String getMatrixTestOne (#MatrixVariable(pathVar = "vars", required = true) String v1,
#MatrixVariable(pathVar = "vars", required = true) String v2,
#MatrixVariable(pathVar = "vars", required = true) String v3) {
return v1 + v2 + v3;
}
#ApiOperation(value = "Matrix Variable Test 2.")
#GetMapping(value = "/matrix_test_two/{vars}", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Map<String, String> getMatrixTestTwo (#MatrixVariable Map<String, String> vars) {
return vars;
}
#Autowired
public void setProjectService(ProjectService projectService) {
this.projectService = projectService;
}
#Autowired
public void setValidatorService(ValidatorService validatorService) {
this.validator = validatorService;
}
}
When I call
GET http://[...]/my_awesome_project/matrix_test_one/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_one/v1=toto
I have the same following error message :
There was an unexpected error (type=Bad Request, status=400). Missing
matrix variable 'v2' for method parameter of type String
But when I call
GET http://[...]/my_awesome_project/matrix_test_one/v2=titi
OR
GET http://[...]/my_awesome_project/matrix_test_one/[anything except "v1=*"]
I have the same following error message :
There was an unexpected error (type=Bad Request, status=400).
Missing matrix variable 'v1' for method parameter of type String
So Spring seems ok to get the first element of the matrix variable but stop then.
So I keep trying with the second test function :
GET http://[...]/my_awesome_project/matrix_test_two/v1=toto;v2=titi;v3=tata
OR
GET http://[...]/my_awesome_project/matrix_test_two/v1=toto
Return :
{
"v1": "toto"
}
-
GET http://[...]/my_awesome_project/matrix_test_two/v2=titi;v1=toto;v3=tata
Return :
{
"v2": "titi"
}
So this behavior seems to confirm my fears.
Did you see something I missed in order to enable matrix variable support (maybe related to semicolon) ?

The mentioned example is using Spring Boot. Launching the exmaple via Spring Boot works as expected. Without Spring Boot it doesn't work out of the box as it is explained in this Q&A. The reason is the UrlPathHelper injected from the #Configuration (point 2 in the exmple) isn't used to process the request. A default instance of UrlPathHelper is used and therefore urlPathHelper.shouldRemoveSemicolonContent() returns true. This removes the matrix variables from the request.
EDIT:
I debugged into it and it turned out that there are two beans of type RequestMappingHandlerMapping.
So I tried this configuration:
#Configuration
#ComponentScan(basePackageClasses = { WebMvcConfiguration.class })
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
#Bean("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
#Qualifier("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping")
public RequestMappingHandlerMapping fullyQualifiedRequestMappingHandlerMapping() {
return requestMappingHandlerMapping();
}
#Bean
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
requestMappingHandlerMapping.getUrlPathHelper().setRemoveSemicolonContent(false);
return requestMappingHandlerMapping;
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = configurer.getUrlPathHelper();
if (urlPathHelper == null) {
urlPathHelper = new UrlPathHelper();
}
urlPathHelper.setRemoveSemicolonContent(false);
}
}
But the fully qualified bean wasn't created by the first method. This bean is processing the request. So the matrix variables were still removed.
As I was unable to provide a factory method for the bean I tried to modify the state of the bean:
#Component
public class Initializer {
#Autowired
private ApplicationContext appContext;
#PostConstruct
protected void init() {
initUrlPathHelper("org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping");
initUrlPathHelper("requestMappingHandlerMapping");
}
protected void initUrlPathHelper(String beanName) {
AbstractHandlerMapping b = (AbstractHandlerMapping) appContext.getBean(beanName);
b.setUrlPathHelper(urlPathHelper());
}
public UrlPathHelper urlPathHelper() {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setRemoveSemicolonContent(false);
return urlPathHelper;
}
}
This did it for me. The matrix variables have been mapped.

Related

Spring AOP doesn't work with #Component class

I'm using Spring AOP for exception handling but there is one point that I guess my component class is out of Spring Proxy so Spring AOP annotation that I created doesn't work in that class.
#Configuration
#AllArgsConstructor
public class MGRuleConfig {
private final GRepository repository;
private final GInitializer initializer;
private final GMapper mapper;
#Bean
#Qualifier("mRules")
public List<GRules> mRules(){
SSRule rule1 = new SSRule();
CSRule rule2 = new CSRule();
MPRule rule3 = new MPRule();
EGRule rule4 = new EGRule();
return List.of(rule1, rule2, rule3, rule4);
}
#Bean
public GService gService() {
return new MGServiceImpl(repository, initializer, mapper);
}
}
Then I have this service;
#Service
#RequiredArgsConstructor
public class MGServiceImpl implements GService {
............
#Override
public GaDTO executeRules(String gId, Integer pN) {
Ga ga = repository.findById(gId);
GaDTO gaDTO = mapper.toDTO(ga);
List<GaRules> mRules = (List<GaRules>) applicationContext.getBean("mRules");
mRules.forEach(rule -> rule.apply(gaDTO, pN));
repository.save(mapper.toEntity(gaDTO));
return gaDTO;
}
I need to put my exception handling annotation into that apply method but aspect doesn't work in that method.
#Component
public class SSRule implements GaRules {
#Override
#IPException
public void apply(GaDTO gaDTO, Integer pN) {
PDTO p1 = gaDTO.getP1();
PDTO p2 = gaDTO.getP2();
if (PTEnum.P_1.equals(gaDTO.getPT())) {
sS(gaDTO, pN, p1, p2);
} else {
sS(gaDTO, pN, p2, p1);
}
}
Annotation doesn't work in there. Here's my aspect class;
#Aspect
#Component
public class IPExceptionAspect {
#Around("execution(public * c.m.s.r.i.SSRule.apply(..)) && " +
"#annotation(c.m.s.i.a.IPException)")
public Object checkIP(ProceedingJoinPoint pjp) throws Throwable {
pjp.proceed();
return pjp;
}
}
So, what should I do to make IPException annotation and my Spring AOP work and why doesn't it work?
The problem is your code, you are creating instances of those rules yourself inside a bean method and expose them as a List. Which means the bean is of type List not your own SSRule and thus it won't work.
Instead make an #Bean method, or use the detected instance to inject into the list. As your SSRule is annotated you will already have an instance, just inject that into your #Bean method.
Bean
#Qualifier("mRules")
public List<GRules> mRules(SSRule rule1){
CSRule rule2 = new CSRule();
MPRule rule3 = new MPRule();
EGRule rule4 = new EGRule();
return List.of(rule1, rule2, rule3, rule4);
}
Now you will get the Spring managed instance which will have AOP applied.
Although I would hardly call this AOP as it is too specific for one class (not really crosscutting in that regard).

Calling service depend on PathVariable using single controller

I need to call implementation service from single controller depend on PathVariable
/{variable}/doSomething
public void controller(#PathVariable("variable") variable)
if variable == 1
call service1Impl();
else if variable == 2
call service2Impl();
but I need my controller plain like this and not using if, else
public void controller(...) {
call service();
}
I need to find some solution for auto-configuration my app when getting any PathVariable, it should know which service needs to call.
I try to using
load Config.class as context - #Configuration
#Configuration
public class AppConfig {
#Bean(name = "variableValue1")
public DummyService getService1() {
return new DummyServiceImpl();
}
#Bean(name = "variableValue2")
public AnotherService getService2() {
return new AnotherServiceImpl();
}
but in controller I need to load this config as context then its not plain enough
bean factory
its work but my controller not enough plain for me
I need to do like this one but it must based on PathVariable not property name.
#Configuration
public class GreetingServiceConfig {
#Bean
#ConditionalOnProperty(name = "language.name", havingValue = "english", matchIfMissing = true)
public GreetingService englishGreetingService() {
return new EnglishGreetingService();
}
#Bean
#ConditionalOnProperty(name = "language.name", havingValue = "french")
public GreetingService frenchGreetingService() {
return new FrenchGreetingService();
}
}
------------------------------------------------
#RestController
public class HomeController {
#Autowired
GreetingService greetingService;
#RequestMapping("/")
public String home() {
return greetingService.greet();
}
}
So based on the pathvariable, the specific method needs to be executed..
This is just a suggestion, since you dont want to go for if else
you can use Hashmap for this,
HashMap<Integer, Runnable> hm = new HashMap<Integer, Runnable> ();
For example,
pathvariable is 1 -> method be executed is method1()
pathvariable is 2 -> method be executed is method2()
hm.put(1, method1())
hm.put(2, method2())
So in controller,
if PathVariable is 1,
hm.get(1).run(); // hm.get(variable).run()

Required a bean of type 'org.springframework.web.client.RestOperations' that could not be found

Hi I have this project running on Spring boot. When I tried to run, it give me the following error:
Description:
Field clientCredentialsApi in com.mycompany.microservice.myproject.bot.controller.BotCommandController required a bean of type 'org.springframework.web.client.RestOperations' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.web.client.RestOperations' in your configuration.
Here is my Code:
Application.java
package com.mycompany.microservice.myproject
//some imports
#SpringBootApplication`
#ComponentScan(basePackages = "com.mycompany.*")
public class Application {
public static void main(String[] args) {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
And here is the Controller:
BotCommandController.java
package com.mycompany.microservice.myproject.bot.controller;
//some imports
#RestController
#RequestMapping("/bot-command")
public class BotCommandController {
#Autowired
private RestOperations clientCredentialsApi;
#RequestMapping(value = "/sraix", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody String sraixCommand(#RequestParam(name = "input", required = false) final String input,#RequestParam(name = "cs", required = false) final String cs) throws Exception {
final UserApiObject userApiObject = clientCredentialsApi.getForObject(env.getProperty("gms.location") + "/rest/user/" + userId, UserApiObject.class);
return userApiObject.getRole();
}
You are trying to autowire bean that you didn't define or implemented. RestOperations is an interface and has to be implemented.
#Autowired
private RestOperations clientCredentialsApi;
Spring is looking up for the classes annotated with #Bean or #Component or #Service to inject their references. In your case, you didn't define bean RestOperations and Spring can't inject it into BotCommandController.
Something like this:
#Bean
RestOperations rest(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.basicAuthorization("user", "password").build();
}

Custom Messages in Bean Validation using Spring’s Validator interface

I'm using Spring Boot 1.3.5 with Rest Controllers and everything is working fine.
I am also using Spring's validation sample techniques from the official documentation (JSR-303 Bean Validation API and Spring's validator interface, i tried both and faced the same problem) and the validations are working, but I am not able to configure custom messages.
I have configured a messages.properties file, and I can access the messages on this file just fine. However this validation seems not to be capable of reading or accessing my messages source (messages.properties) configured automatically via spring boot.
I can access the messages directly from the messages source object injected in controller via #Autowired (there's a comment in the code). However, the binding result of the Spring's validator interface or the JSR-303 Bean Validation seems to not be capable of accessing the messages.properties loaded in MessageSource. The result I have is that my errors have codes but don't have default messages.
Here is my Application class:
#SpringBootApplication
#ImportResource({ "classpath:security/cas-context.xml", "classpath:security/cas-integration.xml",
"classpath:security/security.xml" })
#EnableAutoConfiguration(exclude = VelocityAutoConfiguration.class) // http://stackoverflow.com/questions/32067759/spring-boot-starter-cache-velocity-is-missing
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Nfse nfseService() {
return new NfseImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), nfseService());
endpoint.publish("/nfseSOAP");
return endpoint;
}
}
Here is my Bean:
public class Protocolo {
private Long id;
#NotNull
#Min(1)
#Max(1)
private String protocolo;
private StatusProtocoloEnum status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProtocolo() {
return protocolo;
}
public void setProtocolo(String protocolo) {
this.protocolo = protocolo;
}
public StatusProtocoloEnum getStatus() {
return status;
}
public void setStatus(StatusProtocoloEnum status) {
this.status = status;
}
}
Here is My rest controller:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getAuthorities());
System.out.println(messageSource.getMessage("protocolo.tamanho", null, null));
// IN THIS PART I'M ABLE TO PRINT THE MESSAGE IF VALIDATION IS DISABLED
System.out.println(p.getProtocolo());
}
}
So, this code works fine and the method is not called since i'm calling the method with a invalid Protocolo. However, my angularJS client receives the response with the errors codes populated but with all the default messages empty since the validation is not seeing my loaded messages.properties.
Is there a way to make my Spring validation Interfaces or JSR-303 validation incorporate the loaded message.properties (messagesource) in spring boot ? How can i correct this ? If it's necessary i can paste my code sample of Spring Validation interfaces also.
Thank's a lot,
Tarcísio.
TEST CODE:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
System.out.println(messageSource.getMessage("Min.protocolo.protocolo", null, null));
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
System.out.println(bindingResult.getFieldError().getCode());
}
System.out.println(p.getProtocolo());
}
}
Edit:
Known Bug in Spring Boot 1.5.3 see https://github.com/spring-projects/spring-boot/issues/8979
In Spring Boot since 1.5.3 you need to do this
#Configuration
public class ValidationMessageConfig {
#Bean
public LocalValidatorFactoryBean mvcValidator(MessageSource messageSource) {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
and then it will work.
With version 1.5.2 and before you can extend WebMVcConfigurerAdapter
#Configuration
public class ProfileMvcConfig extends WebMvcConfigurerAdapter {
private MessageSource messageSource;
#Autowired
public ProfileMvcConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This method is overridden due to use the {#link MessageSource message source} in bean validation.
*
* #return A Validator using the {#link MessageSource message source} in bean validation.
*/
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
also see the documentation
In Spring Boot applicaton MessageSource is configured with a MessageSourceAutoConfiguration and you don't need to autowire it. For jsr303, create proper key-value pair in the messages.properties file. For "protocolo" field, you should have following values in property file.
NotNull.protocolo.protocolo=Field cannot be left blank
Min.protocolo.protocolo=Minimum value must be {1}
You can also check messages from property file like below in your code.
public void testar(#Valid #RequestBody Protocolo p,BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}
}
you should have following values in property file:
Min.protocolo.protocolo=Minimum value must be {1}
then in the controller you obtain the message by calling function getMessage from messageSource object
Test code:
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getFieldErrors().forEach(fieldError ->
System.out.println(messageSource.getMessage(fieldError, Locale.getDefault()))
);
}
System.out.println(p.getProtocolo());
}
I solved this in custom message in Spring validation read the last part of my answer.
Check this example as well.
I used a custom validator with custom annotation. I needed to change code in my custom validator.
public class PersonValidator implements ConstraintValidator {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
if (somethingIsInvalid()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Something is invalid.").addConstraintViolation();
return false;
}
return true;
}
}

Why do I get 404 for rest with spring-boot

I am implementing rest services with Spring Boot. The entity classes are defined in a separate package. So I added that with Component annotation in Application.java.
#Configuration
#EnableAutoConfiguration
#ComponentScan("org.mdacc.rists.cghub.model")
#EnableJpaRepositories(basePackages = "org.mdacc.rists.cghub.model")
public class Application
{
public static void main( String[] args )
{
SpringApplication.run(Application.class, args);
}
}
Here is my controller class:
// SeqController.java
#RestController
public class SeqController {
#Autowired
private SeqService seqService;
#RequestMapping(
value = "/api/seqs",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<SeqTb>> getSeqs() {
List<SeqTb> seqs = seqService.findAll();
return new ResponseEntity<List<SeqTb>>(seqs, HttpStatus.OK);
}
}
I also created a JPA data repository that extends JPARepository in which I added custom query code.
// SeqRepository.java
#Repository
public interface SeqRepository extends JpaRepository<SeqTb, Integer> {
#Override
public List<SeqTb> findAll();
#Query("SELECT s FROM SeqTb s where s.analysisId = :analysisId")
public SeqTb findByAnalysisId(String analysisId);
}
Below is the servicebean class that implements a service interface
// SeqServiceBean.java
#Service
public class SeqServiceBean implements SeqService {
#Autowired
private SeqRepository seqRepository;
#Override
public List<SeqTb> findAll() {
List<SeqTb> seqs = seqRepository.findAll();
return seqs;
}
public SeqTb findByAnalysisId(String analysisId) {
SeqTb seq = seqRepository.findByAnalysisId(analysisId);
return seq;
}
}
When I started the application and type the following url in the browser "http://localhost:8080/api/seqs" , I got 404 error. What did I miss?
Edit #1:
I decided to take out the JPA repository stuff and change the controller class to the following:
#RestController
//#RequestMapping("/")
public class SeqController {
private static BigInteger nextId;
private static Map<BigInteger, Greeting> greetingMap;
private static Greeting save(Greeting greeting) {
if(greetingMap == null) {
greetingMap = new HashMap<BigInteger, Greeting>();
nextId = BigInteger.ONE;
}
greeting.setId(nextId);
nextId = nextId.add(BigInteger.ONE);
greetingMap.put(greeting.getId(), greeting);
return greeting;
}
static {
Greeting g1 = new Greeting();
g1.setText("Hello World!");
save(g1);
Greeting g2 = new Greeting();
g1.setText("Hola Mundo!");
save(g2);
}
#RequestMapping(
value = "/api/greetings",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Collection<Greeting>> getGreetings() {
Collection<Greeting> greetings = greetingMap.values();
return new ResponseEntity<Collection<Greeting>>(greetings, HttpStatus.OK);
}
}
When I started the application and put "localhost:8080/api/greetings" in my browser I still got 404.
==>Did you make sure that your Spring Boot application class and your Rest Controller are in the same base package? For Example if your package for Spring Boot application class is com.example.demo, then your Rest Controller should be in same base package as com.example.demo.controller.
==>I think that is the reason boot is unable to map to the uri of your rest controller. Because #SpringBootApplication has #ComponentScan and #Configuration embedded in it already. Try doing this. I hope it works.
If spring boot starter web is not there in your pom.xml then add the same as the reason could be the code not being able to map the endpoints.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The first thing I would try is to put #RequestMapping("/") on the class definition of the controller. Keep the same value on the method.
Another thing, unrelated to your problem, is that you do not need to define that custom query. JPA is actually smart enough to do the query you defined just by using that method name. Check out the findByLastName example here: https://spring.io/guides/gs/accessing-data-jpa/.

Categories