Spring MVC: Ambiguous RequestMappings after moving to JavaConfig - spring

After moving from XML setup to a JavaConfig setup many of our RequestMappings have broken and now return ambiguous method errors. Our methods rely on #PathVariable's with regular expressions to determine which to call. For example:
#RequestMapping(value={"/{id:\\d+}/boats"})
public String getBoatsById(#PathVariable("id") Long id, Model model,
HttpServletRequest request) throws Exception {...}
#RequestMapping(value={"/{id}/boats"})
public String getBoatsByName(#PathVariable("id") String id, Model model,
HttpServletRequest request) throws Exception {...}
This use to work with out issue but using the new JavaConfig setup versus the XML setup it breaks with the ambiguous errors due to the mappings.
The JavaConfig class starts as such:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.example", excludeFilters = { #ComponentScan.Filter( Configuration.class ) })
public class WebConfig extends WebMvcConfigurationSupport
Would it have anything to do with the XML setup using the AnnotationMethodHandlerAdapter versus the JavaConfig class now using the recommended RequestMappingHandlerAdapter? Is there a setting I am missing?

Just browsed the source code, AnnotationMethodHandlerAdapter uses a different RequestMappingInfo than RequestMappingHandlerAdapter. The former ignores the pattern/path you have specified when checking for equality while the latter honors it. That's why you are having ambiguous mapping errors. Not sure if it is a bug or not, probably it's good to ask the people at the spring-contrib mailing list.
EDIT
Probably it's a good idea to change your mappings. The {name:reg_exp} syntax was introduced not to solve ambiguity. Excerpt from the official documentation:
Sometimes you need more precision in defining URI template variables.
Consider the URL "/spring-web/spring-web-3.0.5.jar". How do you break
it down into multiple parts?
The #RequestMapping annotation supports the use of regular expressions
in URI template variables. The syntax is {varName:regex} where the
first part defines the variable name and the second - the regular
expression.For example:
#RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}{extension:\.[a-z]+}")
public void handle(#PathVariable String version, #PathVariable String extension) {
// ...
}
}
Your handler methods were ambiguous from the start, you only provided a hackish way to solve the ambiguity.

Related

Remove a Spring ConversionService across the entire application

I am running with Spring 3.2.18 (I know, it's old) with Spring MVC. My issue is that there is at least one default request parameter conversion (String -> List) that fails when there is actually only one item in the array, but it has commas. This is because the default conversion built into Spring will see it as a comma-separated list.
I am NOT using Spring Boot, so please avoid answers that specifically reference solutions using it.
I tried adding a #PostConstruct method to a #Confuguration class as follows:
#Configuration
public class MyConfig {
#Autowired
private ConfigurableEnvironment env;
#PostConstruct
public void removeConverters() {
ConfigurableConversionService conversionService = env.getConversionService();
conversionService.removeConvertible(String.class, Collection.class);
}
}
This runs on startup but the broken conversion still occurs. I put a breakpoint in this method, and it is called only once on startup. I verified that it removed a converter that matched the signature of String -> Collection.
The following works, but when I put a breakpoint in the #InitBinder method it acts like it gets called once for every request parameter on every request.
#ControllerAdvice
public class MyController {
#InitBinder
public void initBinder(WebDataBinder binder) {
GenericConversionService conversionService = (GenericConversionService) binder.getConversionService();
conversionService.removeConvertible(String.class, Collection.class);
}
}
As I said the second one works, but it makes no sense that the offending converter has to be removed for every request made to that method - let alone for every request parameter that method takes.
Can someone please tell me why this only works when the removal is incredibly redundant? Better yet, please tell me how I'm doing the one-time, application-scope removal incorrectly.

How to choose bean implementation at runtime for every http request

I am having two implementations of my component.
public interface MyComponent {
}
imple1
#Component("impCompf")
#Lazy
#RequestScope
public class ImpComp1 implements MyComponent {
}
imple2
#Component("impComps")
#Lazy
#RequestScope
public class ImpComp2 implements MyComponent {
}
What I did so far is to create two conditions like so:
imple1
public class FirstCondition implements Condition {
#Override
public boolean matches(ConditionContext arg0, AnnotatedTypeMetadata arg1) {
return staticVariable.contains("impCompf");
}
}
Same goes for imple2
and define a configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(FirstCondition .class)
#Primary
public MyComponent getComp1() {
return new ImpComp1();
}
public static String staticVariable= "impCompf";
and in My main controller:
#RequestMapping(value="api/{co}", method=RequestMethod.POST)
public ResponseEntity<Modelx> postSe(#PathVariable("co") String co) {
if(co.contains("impCompf"))
staticVariable = "impCompf";
else (co.contains("impComps"))
staticVariable = "impComps";
What I want: for every http request I want to load proper implementation
But however what I am getting is the implementation defined first in the static variable.
If is there another elegant and better way, i'd like to know about it.
I think there is some confusion here about the purpose of the conditions. These aren't being used at the time your requests arrive to autowire the candidate bean into your controller. These are being used when the application is started to configure the application context based on the environment and classpath etc...
There is no need for the conditional classes that you have created. This is defining the configuration of the beans when the context starts and not on a per request basis at runtime.
The use of the static variable is also problematic is a scenario with one or more concurrent requests or in a case where multiple threads may observe different values unless some other mechanism in the java memory model is being used (such as volatile or establishing a happens before relationship, e.g. with sychnronized)
There are a number of ways to do what you appear to be trying to achieve. Since ultimately, you appear to be using a path parameter supplied by a client to determine which service you want to invoke you could use a classic factory pattern to return the correct interface implementation based on the string input programmatically.
Alternatively you could create two distinct controller methods which are distinguished by a query parameter or endpoint name or path match etc. You could then have the appropriate service injected by a qualified bean name
Although perhaps generally recommended, you could also inject an application context instance and search the it looking for the relevant bean by name or class: https://brunozambiazi.wordpress.com/2016/01/16/getting-spring-beans-programmatically/ - although This is more cumbersome and you'd need to handle things like org.springframework.beans.factory.NoSuchBeanDefinitionException or casting in some cases - best avoided in favour of one of the other methods.

Spring-web tries to find resource named with informed path variable

Using spring-web, I am mapping a method to receive a request containing dots "." on the path:
#RequestMapping(value = "download/{id:.+}", method = RequestMethod.GET, produces = "application/xls")
public String download(#PathVariable(value = "id") String id) { ... }
For example, /download/file.xls should be a valid address. But when I try to access that address, Spring returns Could not find acceptable representation as if it was trying to find a resource named file.xls.
Spring shouldn't execute download method rather than try to find a resource named as the path variable?
Obs.: my application is a spring-boot application.
Your #RequestMapping says it produces "application/xls", but your return type is a String and you haven't annotated the return type with #ResponseBody.
If you want to return an Excel spreadsheet, you need to produce that spreadsheet on the server and return it as a byte[] from your request mapping. I'm not sure how or why you'd return a String, unless you're controller is a simple #Controller and you're returning the view name.
Have you tried configuring your RequestMappingHandlerMapping
handler.setUseSuffixPatternMatch( false )
(I was configuring my RequestMappingHandlerMapping anyway, so for me I just needed to add that line - chances are you may be letting Spring Boot autoconfig that class).
See https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseRegisteredSuffixPatternMatch-boolean-
Possibly you may need to turn off content negotiation as well - I can't remember exactly what Spring Boot default content negotiation is, but it might be affecting your case.
#Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
}
Worth noting that if you are working on a wider/existing application then both these configurations have possible implications more widely, so if that is the case then tread carefully!

Get Request fails for .com email addresses because Spring interpretes it as a extensions

(See edit part below 20.08.2015)
I had a similar problem recently (Get request only works with trailing slash (spring REST annotations)) and the solution was to add a regex to the value of #RequestMapping (see also Spring MVC #PathVariable getting truncated).
But now we have realised that the problem still exists, but only for email addresses ending on .com or .org. This is very weird.
Shortly, I use Spring Annotations building a REST Service. I have a GET request with three parameters, the last is an email.
My Controller:
#RequestMapping(method = GET, value = "/{value1}/{value2}/{value3:.+}",
produces = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")
public ResponseEntity<MyResponseType> getMyResource(
#ApiParam(value = "...",...)
#PathVariable("value1") String value1,
#ApiParam(value = "...",...)
#PathVariable("value2") String value2,
#ApiParam(value = "...",...)
#PathVariable("value3") String value3) {
//...
}
If I call:
http://myserver:8080/myresource/value1/value2/value3
with value3= email#domain.de / co.uk / .name / .biz /.info, there is no problem at all.
But with certain top level domains (.com, .org so far) I receive Http Status Code 406 (Not accepted).
It works if I add a trailing slash:
http://myserver:8080/myresource/value1/value2/value3/
As we use swagger and swagger does not add trailing slashes, adding a trailing slash is not an option.
What might cause this problem?
I use an ErrorHandler which extends ResponseEntityExceptionHandler.
I debugged it and found that HttpMediaTypeNotAcceptableException ("Could not find acceptable representation") is thrown. But I can't find out yet who is throwing it and why.
edit
I found out that the path http://myserver:8080/myresource/value1/value2/myemail#somewhere.com
is interpreted as file, and "com" is the file extension for the media type "application/x-msdownload", so in Spring's class ProducesRequestCondition.ProduceMediaTypeExpression in the method matchMediaType the line "getMediaType().isCompatibleWith(acceptedMediaType)" fails, because the actually produced media type is "application/json;charset=UTF-8", not "application/x-msdownload".
So the questions changes to: How can I make Spring understand, that .com is not a file extension?
This thread helped me:
SpringMVC: Inconsistent mapping behavior depending on url extension
Obviously this is not a bug but a "feature" and there are two different ways to disable it. I used the annotation version:
#Configuration
#EnableWebMvc
public class RestCommonsMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}
I came across this problem when upgraded from spring 3.1.2 to 3.2.7. The answer by Nina helped me (which should be marked as the answer). But instead of extending the WebMvcConfigurerAdapter I did it in my WebConfig that extended WebMvcConfigurationSupport.
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
super.configureContentNegotiation(configurer);
configurer.favorPathExtension(false).favorParameter(true);
}
}

Why does Spring allow controller annotated request mappings on private methods?

Just came accross this today in a Spring MVC cotnroller class,
#RequestMapping(value = { "/foo/*" }, method = { RequestMethod.GET})
private String doThing(final WebRequest request) {
...
return "jsp";
}
This is making it a bit harder to write a test, I'll probably change it to public but what's the point of allowing mappings on private methods?
Java does not provide a mechanism for limiting the target of annotations based on access modifier.
As #smp7d stated, Java does not limit the target of annotations based on access modifiers, but syntactically speaking, #RequestMapping should not work on private methods. Also we cannot limit this, since it would break the backward compatibility. So, you can either go for defining your methods as public or you can create your own custom implementation.
Take a look at this: Spring's #RequestMapping annotation works on private methods

Resources