Controller Inheritance and Ambiguous Mappings with URL Versioning in Spring MVC - spring-mvc

I am trying to setup versioned services with Spring MVC, using inheritance to extend older controllers to avoid rewriting unchanged controller methods. I've based my solution on a previous question about versioning services, however I've run into a problem with ambiguous mappings.
#Controller
#RequestMapping({"/rest/v1/bookmark"})
public class BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public Map<String, String> writeBookmark(#RequestParam String parameter) {
// Perform some operations and return String
}
}
#Controller
#RequestMapping({"/rest/v2/bookmark"})
public class BookmarkJsonControllerV2 extends BookmarkJsonController {
#ResponseBody
#RequestMapping(value = "/write", produces = "application/json", method = RequestMethod.POST)
public BookmarkJsonModel writeBookmark(#RequestBody #Valid BookmarkJsonModel bookmark) {
// Perform some operations and return BookmarkJsonModel
}
}
With this setup I get IllegalStateException: Ambiguous mapping found. My thought regarding this is that because I have two methods with different return/argument types I have two methods in BookmarkJsonControllerV2 with the same mapping. As a workaround I attempted to override writeBookmark in BookmarkJsonControllerV2 without any request mapping:
#Override
public Map<String, String> writeBookmark(#RequestParam String parameter) {
return null; // Shouldn't actually be used
}
However, when I compiled and ran this code I still got the exception for an ambiguous mapping. However, when I hit the URL /rest/v2/bookmark/write I got back an empty/null response. Upon changing return null to:
return new HashMap<String, String>() {{
put("This is called from /rest/v2/bookmark/write", "?!");
}};
I would receive JSON with that map, indicating that despite not having any request mapping annotation, it is apparently "inheriting" the annotation from the super class. At this point, my only "solution" to future-proofing the extension of the controllers is to make every controller return Object and only have the HttpServletRequest and HttpServletResponse objects as arguments. This seems like a total hack and I would rather never do this.
So is there a better approach to achieve URL versioning using Spring MVC that allows me to only override updated methods in subsequent versions or is my only real option to completely rewrite each controller?

For whatever reason, using the #RequestMapping annotation was causing the ambiguous mapping exceptions. As a workaround I decided to try using springmvc-router for my REST services which would allow me to leverage inheritance on my controller classes so I would not have to reimplement endpoints that did not change between versions as desired. My solution also allowed me to continue using annotation mappings for my non-REST controllers.
Note: I am using Spring 3.1, which has different classes for the handler mappings than previous versions.
The springmvc-router project brings the router system from the Play framework over to Spring MVC. Inside of my application-context.xml, the relevant setup looks like:
<mvc:annotation-driven/>
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
<bean class="org.resthub.web.springmvc.router.RouterHandlerMapping">
<property name="routeFiles">
<list>
<value>routes/routes.conf</value>
</list>
</property>
<property name="order" value="0" />
</bean>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="order" value="1" />
</bean>
This allows me to continue using my annotated controllers alongside the router. Spring uses a chain-of-responsibility system, so we can assign multiple mapping handlers. From here, I have a router configuration like so:
# Original Services
POST /rest/bookmark/write bookmarkJsonController.write
POST /rest/bookmark/delete bookmarkJsonController.delete
# Version 2 Services
POST /rest/v2/bookmark/write bookmarkJsonControllerV2.write
POST /rest/v2/bookmark/delete bookmarkJsonControllerV2.delete
Alongside controllers looking like:
#Controller
public class BookmarkJsonController {
#ResponseBody
public Map<String, Boolean> write(#RequestParam String param) { /* Actions go here */ }
#ResponseBody
public Map<String, Boolean> delete(#RequestParam String param) { /* Actions go here */ }
}
#Controller
public class BookmarkJsonControllerV2 extends BoomarkJsonController {
#ResponseBody
public Model write(#RequestBody Model model) { /* Actions go here */ }
}
With a configuration like this, the URL /rest/v2/bookmark/write will hit the method BookmarkJsonControllerV2.write(Model model) and the URL /rest/v2/bookmark/delete will hit the inherited method BookmarkJsonController.delete(String param).
The only disadvantage from this comes from having to redefine entire routes for new versions, as opposed to changing the #RequestMapping(value = "/rest/bookmark") to #RequestMapping(value = "/rest/v2/bookmark") on the class.

Related

get or pass values from jsp to spring mvc controller with #RequestParam("VIEW") annotation

Hi everyone I'm trying to get or pass the values from my JSP to my controller because I want to write a method whose main function will be to save a new user in the database but I got an error like this:
(((java.lang.IllegalStateException: Mode mappings conflict between method and type level: [/myPath] versus [VIEW])))
I guess this two annotations, first in the class declaration #RequestMapping("VIEW") and the second in the method declaration for save the new user
#RequestMapping(value="/saveUser", method = RequestMethod.POST)
are in conflict by the use of the same annotation in tow times at the same controller but I have to say that I´ve been tried to remove the #RequestMapping annotation in the class declaration and after that, I get a different error like this:
(((java.lang.IllegalStateException: No portlet mode mappings specified - neither at type nor at method level)))
I don't know if I can use as many necessary controllers for differents operations as I will need, if I can, I will be happy to know the correct way to implement with this technique
Here is my controller:
#Controller
#RequestMapping("VIEW")
public class UsersController {
User currentUser = null;
#RenderMapping
public ModelAndView view(RenderRequest request, RenderResponse response)throws Exception {
ModelAndView modelView = new ModelAndView();
UserDTO usuarioAdd = new UserDTO();
//blah, blah, blah...
return modelView;
}
#RequestMapping(value="/saveUser", method=RequestMethod.POST)
public void saveUser(HttpServletRequest request) {
logger.info("***SAVE USER***);
System.out.println("***SAVE USER***);
}
}

Aspect not getting called in Spring MVC

I have our aspect, annotation & MVC controller written as follows:
Aspect
#Aspect
public class AuditAspect {
#Around(value = "#annotation(com.test.Audit)")
public Object audit(ProceedingJoinPoint pjp) {
System.out.println("Inside the Audit aspect ...");
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable t) {
}
return result;
}
}
The annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Audit
{
AuditType auditType();
}
The controller:
#RestController
#RequestMapping("/patients")
public class PatientController {
#Audit(auditType = AuditType.PATIENT_LIST)
#RequestMapping(value="", method=RequestMethod.GET)
public APIResponse getPatients(HttpServletRequest request, HttpServletResponse response, #RequestParam(required = false, value="audit") String sAudit) {
System.out.println("Inside getPatients ...");
return null;
}
}
However, the aspect's audit method is not getting called whenever I make rest requests.
Looked around for some help. Found few posts where there were mentions of AspectJ not working with Spring MVC controllers. However, I tried this same example with a simple spring MVC application, and the aspect was getting called properly, even if controller methods were annotated. Not sure what is going wrong here. Any pointers/suggestions here would be very helpful.
The sample application I tried didn't have use of spring transaction manager, or integration with hibernate etc... Would that make any difference?
Also, given below is the context file entries:
<aop:aspectj-autoproxy />
<context:component-scan base-package="com.test">
<context:include-filter type="aspectj" expression="com.test.AuditAspect" />
</context:component-scan>
<context:annotation-config />
In order to make Spring AOP work, both your aspect and the target object must be a Spring #Component.

#RequestMapping with placeholder not working

So i have spend hours to try to get the anwser of this post working:
Overriding RequestMapping on SpringMVC controller
But it really is not working. What I have so far:
springmvc-servlet.xml
<context:property-placeholder location="classpath:numbernick.properties"/>
<context:component-scan base-package="com.numbernick" />
<context:annotation-config />
And I've got a Controller:
#Value("${requestmapping.test}")
private String test;
#RequestMapping("${requestmapping.test}.html")
public ModelAndView test() {
ModelAndView mav = new ModelAndView();
mav.setViewName(test.html);
log.debug("Test: "+test);
return mav;
}
numbernick.properties:
requestmapping.test=myUrl
This should work fine. When I call the page, I get a logmessage saying "Test: myUrl"
. BUT! this comes when I call "/${requestmapping.test},html". And it should work with calling "/myUrl.html". I have absolutely no Idea why it is this way. Obviously the PropertyPlaceholder works and doesn't work at the same time. (BTW: It is a nested RequestMapping. But it also doesn't work at topLvl-RequestMapping as well)
How can this be and what can I do to fix this? I'm currently working with spring verion 3.2.8
I had this problem also and solved it once I realized that a PropertyPlaceholderConfigurer bean wasn't loaded into the context of the module where many of the placeholders existed.
Simple solution was to refactor our externalized configuration. In the end, I moved the #PropertySources definition and PropertyPlaceholderConfigurer bean to a common module and all is well:
#Configuration
#PropertySources(value = {#PropertySource("classpath:app-config.properties")})
public class ExternalizedConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
The request mappings like this work as expected now:
#RequestMapping(value="/${foo.bar.rest_proxy_uri}/**", method = RequestMethod.GET)
In fact, on server startup, you will see the placeholders have been resolved:
2015-05-06 16:21:52 INFO RequestMappingHandlerMapping:220 - Mapped "{[/restProxy/**],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.lang.String> foo.bar.web.controllers.RestfulFooBarProxyController.proxyGet(javax.servlet.http.HttpServletRequest)

Spring type conversion not taking effect

I have a lot of Enums implementing an Interface called Codeable. I want to do a reverse look up when deserializing from json and trying to use a ConverterFactory
public class StringToCodeableConverterFactory implements ConverterFactory<String, Codeable> {
#Override
public <T extends Codeable> Converter<String, T> getConverter(
Class<T> targetType) {
return new StringToCodeableConverter<T>(targetType);
}
private final class StringToCodeableConverter<T extends Codeable> implements Converter<String, T> {
private Class<T> enumType;
public StringToCodeableConverter(Class<T> enumType) {
this.enumType = enumType;
}
#Override
public T convert(String source) {
return CodeableUtil.get(this.enumType, source);
}
}
}
Here's the spring config
<!-- Custom Converters from String to Java Type-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.duetto.model.StringToCodeableConverterFactory" />
</list>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService">
After some digging, I figured out Spring is taking the default StringToEnumConverterFactory instead of my StringToCodeableConverterFactory, why is it this way? How can I make my take precedence over the other one?
What I noticed happening was that the DefaultConversionService's defaults included a StringToEnumConversion which appears first in the service's List of possible converters. As such, mine was never being hit, and the standard Enum conversion was being attempted every time.
My workaround was to:
Unregister the default string-to-enum converter - registry.removeConvertible(String.class, Enum.class) where registry is an instance of FormatterRegistry
Write a string-to-mycustomenum converter
Write an all-encompassing string-to-enum converter that did a type check (MyEnumType.class.isAssignableFrom(targetType)) and delegated to either my custom converter or the default string-to-enum converter depending on the result
Note this approach has several problems, among them: StringToEnumConverter is a package-private class so I had to copy-paste it into my own code base. Additionally, this can't be the desired approach to solving this problem; it isn't very "springy".
Would love to hear alternative answers for this.
Worth noting, I'm using Spring 3.2.6
Major Update
I found a much cleaner way, note that I'm using annotation config rather than xml but the principals should be the same.
In Spring's documentation, I found:
GenericConversionService is a generic implementation designed to be
explicitly configured, either programatically or declaratively as a
Spring bean. DefaultConversionService is a subclass that pre-registers
the common Converters in the core.converter package as a convenience.
So, I now have overrides configured as follows:
#Override
public FormattingConversionService mvcConversionService() {
// use FormattingConversionService here rather than GenericFormattingConversionService (the default)
// because it does not automatically register defaults
FormattingConversionService conversionService = new FormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
#Override
protected void addFormatters(FormatterRegistry registry) {
// register custom enum handler first
registry.addConverterFactory(new MyCustomEnumConverterFactory());
// now add in spring's defaults
DefaultConversionService.addDefaultConverters(registry);
DefaultFormattingConversionService.addDefaultFormatters(registry);
}
Now everything's working and it feels significantly less hacky.

Map url with language identifier

Is there a nice way to resolve locale based on the URL and in the other hand map requests without any additional requirement ?
For example
http://example.com/ru/news
http://example.com/iw/news
and in the controller still use the standard mappings
#Controller
#RequestMapping(value = "/news")
public class NewsController {
// Controller methods ...
}
You could write a custom interceptor that works like LocaleChangeInterceptor
Here's a sample implementation that uses a regex pattern (most of the code is copied from LocaleChangeInterceptor):
public class CustomLocaleChangeInterceptor extends HandlerInterceptorAdapter {
private Pattern localePattern;
public void setLocalePattern(final String localePattern) {
Assert.isTrue(localePattern.matches(".*\\(.*\\).*"), "Your pattern needs to define a match group");
this.localePattern = Pattern.compile(localePattern);
}
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws ServletException {
final String pathTranslated = request.getPathTranslated();
if (pathTranslated != null) {
final Matcher matcher = localePattern.matcher(pathTranslated);
if (matcher.find()) {
final String newLocale = matcher.group(1);
if (newLocale != null) {
final LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
}
final LocaleEditor localeEditor = new LocaleEditor();
localeEditor.setAsText(newLocale);
localeResolver.setLocale(request, response, (Locale) localeEditor.getValue());
}
}
}
// Proceed in any case.
return true;
}
}
Wire it like this:
<bean id="localeChangeInterceptor"
class="foo.bar.CustomLocaleChangeInterceptor">
<property name="localePattern" value="\b([a-z]{2})\b"/>
</bean
I'm not aware of an out-of-the-box solution for this, but it's easy enough to implement using a custom interceptor and some wisely chosen mappings.
Write an implementation of HandlerInterceptor which implements preHandle so that the locale string is extracted from the request URI, and then tag the request with that locale (see the source code for the similar LocalChangeInterceptor, which does a similar thing to what you need, but uses a request parameter instead of a path variable).
Then wire it up using <mvc:interceptor> e.g.
<mvc:interceptors>
<mvc:interceptor>
<mapping path="/*"/>
<bean class="x.y.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
You can then loosen up the request mapping on your controller to tolerate (and ignore) the locale part of the URI:
#Controller
#RequestMapping(value = "*/news")
public class NewsController {
// Controller methods ...
}
Take a look at http://lrkwz.github.com/URLManagedLocale/ you can simply drop the dependency in your pom file and configure the interceptor and localeresolver.

Resources