I am not able to share the actual code because of corporate policies but below is an example of method structures.
So in the example I want to the cache on the method in Class B to be cleared when the exception is thrown in class A.
NB: I can not move the cache to Class A so that is not a feasible solution.
I have tried reading all answers and posts online to get this working but not able to figure it out.
Please help with suggestions. A
I have set the following properties in application.properties
spring.cache.enabled=true
spring.cache.jcache.config=classpath:cache/ehcache.xml
#EnableCaching
#EnableTransactionManagement
Main Class{
#Autowired
CacheManager cacheManager
#PostConstruct
void postConstruct(){
(JCacheCacheManager)cachemanager).setTransactionAware(true);
}
}
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
//TestCode to test cache clears if exception thrown here
throw new RuntimeException("test");
}
}
#Service
Class B{
#Cacheable("cacheName")
public List<Data> getDataFromSystem(String key){
client call code here
return dataList;
}
}
There should be other ways, but the following could be a valid solution.
The first step will be to define a custom exception in order to be able to handle it later as appropriate. This exception will receive, among others, the name of the cache and the key you want to evict. For example:
public class CauseOfEvictionException extends RuntimeException {
public CauseOfEvictionException(String message, String cacheName, String cacheKey) {
super(message);
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}
// getters and setters omitted for brevity
}
This exception will be raised by your B class, in your example:
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
// Sorry, because in a certain sense you need to be aware of the cache
// name here. Probably it could be improved
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
Now, we need a way to handle this kind of exception.
Independently of that way, the idea is that the code responsible for handling the exception will access the configured CacheManager and trigger the cache eviction.
Because you are using Spring Boot, an easy way to deal with it is by extending ResponseEntityExceptionHandler to provide an appropriate #ExceptionHandler. Please, consider read for more information the answer I provided in this related SO question or this great article.
In summary, please, consider for example:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
private CacheManager cacheManager;
#ExceptionHandler(CauseOfEvictionException.class)
public ResponseEntity<Object> handleCauseOfEvictionException(
CauseOfEvictionException e) {
this.cacheManager.getCache(e.getCacheName()).evict(e.getCacheKey());
// handle the exception and provide the necessary response as you wish
return ...;
}
}
It is important to realize that when dealing with keys composed by several arguments by default (please, consider read this as well) the actual cache key will be wrapped as an instance of the SimpleKey class that contains all this parameters.
Please, be aware that this default behavior can be customized to a certain extend with SpEL or providing your own cache KeyGenerator. For reference, here is the current implementation of the default one provided by the framework, SimpleKeyGenerator.
Thinking about the problem, a possible solution could be the use of some kind of AOP as well. The idea will be the following.
First, define some kind of helper annotation. This annotation will be of help in determining which methods should be advised. For example:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
}
The next step will be defining the aspect that will handle the actual cache eviction process. Assuming you only need to advice Spring managed beans, for simplicity we can use Spring AOP for that. You can use either an #Around or an #AfterThrowing aspect. Consider the following example:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Around("#annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (CauseOfEvictionException e) {
this.cacheManager.getCache(
e.getCacheName()).evict(e.getCacheKey()
);
// rethrow
throw e;
}
}
}
The final step would be annotate the methods in which the behavior should be applied:
#Service
Class A{
#Autowired
B b;
#Transactional
#EvictCacheOnError
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
You may even try generalizing the idea, by providing in the EvictCacheOnError annotation all the necessary information you need to perform the cache eviction:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
String cacheName();
int[] cacheKeyArgsIndexes();
}
With the following aspect:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Autowired
private KeyGenerator keyGenerator;
#Around("#annotation(your.pkg.EvictCacheOnError)")
// You can inject the annotation right here if you want to
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable t) {
// Assuming only is applied on methods
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// Obtain a reference to the EvictCacheOnError annotation
EvictCacheOnError evictCacheOnError = method.getAnnotation(EvictCacheOnError.class);
// Compute cache key: some safety checks are imperative here,
// please, excuse the simplicity of the implementation
int[] cacheKeyArgsIndexes = evictCacheOnError.cacheKeyArgsIndexes();
Object[] args = pjp.getArgs();
List<Object> cacheKeyArgsList = new ArrayList<>(cacheKeyArgsIndexes.length);
for (int i=0; i < cacheKeyArgsIndexes.length; i++) {
cacheKeyArgsList.add(args[cacheKeyArgsIndexes[i]]);
}
Object[] cacheKeyArgs = new Object[cacheKeyArgsList.size()];
cacheKeyArgsList.toArray(cacheKeyArgs);
Object target = pjp.getTarget();
Object cacheKey = this.keyGenerator.generate(target, method, cacheKeyArgs);
// Perform actual eviction
String cacheName = evictCacheOnError.cacheName();
this.cacheManager.getCache(cacheName).evict(cacheKey);
// rethrow: be careful here if using in it with transactions
// Spring will per default only rollback unchecked exceptions
throw new RuntimeException(t);
}
}
}
This last solution depends on the actual method arguments, which may not be appropriate if the cache key is based on intermediate results obtained within your method body.
I want to be able for jackson to parse case insensitive enums. For e.g
public enum OperType {
SUM
PRODUCT
}
i want to accept both "SUM" and "sum" in the POST request.
I am getting hold of objectMapper in Application::run and enabling the setting:
environment.getObjectMapper().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
But this is having no effect!
Jersey doesn't use objectMapper from Dropwizard bootstrap despite what Dropwizard's official documentation might lead one to believe.
Needed to register custom ContextResolver in the Application::run to make it work:
environment.jersey().register(new ObjectMapperContextResolver(injector.getInstance(ObjectMapper.class)));
where:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Man these documentations around the dropwizard ecosystem can be really confusing for someone who isn't as well versed yet!
When implementing RESTful API I wrap all my data in an object so it looks like this.
{error: null, code: 200, data: {...actual data...}}
This results in repetitive code I use everywhere to wrap data:
#Transactional
#RequestMapping(value = "/", method = RequestMethod.GET)
public #ResponseBody Result<List<BookShortDTO>> books() {
List<Book> books = booksDao.readBooks();
return Result.ok(books); // this gets repeated everywhere
}
So the question is how do I modify this (maybe with use of custom HttpMessageConverter maybe some other ways?) to just return booksDao.readBooks() and to get it wrapped automatically.
Like #Ralph suggested you can use a HandlerMethodReturnValueHandler to wrap your handlers return value.
The easiest way to achieve this is by extending RequestResponseBodyMethodProcessor and alter it's behavior a bit. Best is to create a custom annotation to mark your handler methods with. This will make sure your HandlerMethodReturnValueHandler will be called instead of others included by RequestMappingHandlerAdapter by default.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ResultResponseBody {}
Here is a simple implementation of the custom HandlerMethodReturnValueHandler named ResultResponseHandlerMethodProcessor which will support values returned from methods annotated with ResultResponseBody. It's pretty simple. Just override the supportsReturnType() and handleReturnValue() methods to suit your needs (wrap the return value into a Result type).
public class ResultResponseHandlerMethodProcessor extends RequestResponseBodyMethodProcessor {
public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters) {
super(messageConverters);
}
public ResultResponseHandlerMethodProcessor(final List<HttpMessageConverter<?>> messageConverters, final ContentNegotiationManager contentNegotiationManager) {
super(messageConverters, contentNegotiationManager);
}
#Override
public boolean supportsReturnType(final MethodParameter returnType) {
return returnType.getMethodAnnotation(ResultResponseBody.class) != null;
}
#Override
public void handleReturnValue(final Object returnValue, final MethodParameter returnType, final ModelAndViewContainer mavContainer, final NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
super.handleReturnValue(Result.ok(returnValue), returnType, mavContainer, webRequest);
}
}
The only thing left is to add this class to the list of custom HandlerMethodReturnValueHandlers and provide it with a MappingJackson2HttpMessageConverter instance.
#EnableWebMvc
#Configuration
public class ApplicationConfiguration extends WebMvcConfigurerAdapter
#Override
public void addReturnValueHandlers(final List<HandlerMethodReturnValueHandler> returnValueHandlers) {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new MappingJackson2HttpMessageConverter());
returnValueHandlers.add(new ResultResponseHandlerMethodProcessor(messageConverters));
}
}
I think, rather than changing the message converters (which would work), I would use an AOP approach - around advice on all the relevant controller methods would be quite easy to set up. It would also give you a nicer programming model, and finer grained control of which methods are intercepted.
You could use a HandlerMethodReturnValueHandler to replace the result.
The key point is: to replace the return value before delegating the (modified) retunr value to the serialisation.
See this blog: http://martypitt.wordpress.com/2012/11/05/custom-json-views-with-spring-mvc-and-jackson/ for an example how to archive a similar (not the same) goal. It also describe one way to register the HandlerMethodReturnValueHandler (for an other see Bart´s answer)
I'd like to try and convince you that what you are doing is right and does not require any changes.
As you've posted in the comments to your question, you have a number of different Result methods which set the error message, the code, and the data. Something like
Result.ok(data)
Result.forbidden()
Result.badRequest("<Something> caused a syntax error.")
Result.notModified("The entity was not modified.")
I'm assuming these methods are meant to map to the various HTTP status codes, but with custom error messages.
Your #Controller handler methods are meant to handle a request and prepare a response. That's what your method is currently doing and it is very explicit about what it does. The logic about what the Result should be belongs to the handler method, not a HandlerMethodReturnValueHandler, like others are proposing.
I would even suggest using ResponseEntity instead of #ResponseBody. You can return a ResponseEntity and set the HTTP response headers and status code explicitly. You would also set the response body.
Something like
return new ResponseEntity<>(Result.ok(books));
In this case, the default status code is 200.
But if you wanted to use
return Result.forbidden();
you would use
return new ResponseEntity<>(Result.forbidden(), HttpStatus.FORBIDDEN);
Spring will use the same HttpMessageConverter to convert your Result into JSON, but here you will have more control over the HTTP response.
I think it is necessary to
replace the default RequestResponseBodyMethodProcessor with you owner
processor
, otherwise the default RequestResponseBodyMethodProcessor will take control of handling return value.
Based on other issues that were actually resolved in Jersey 2.6, I suspect this might be a Jersey bug, but I wanted to vet it here first.
The following works as expected:
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper;
#Value("${json.prettyPrint}")
private boolean prettyPrint = false;
public ObjectMapperResolver() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, prettyPrint);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
/**
* Push Joda de/serializers into the actual mapper
*/
#PostConstruct
private void configureJodaSerializers() {
mapper.registerModule(new JodaModule()
// Our deserializers are more forgiving
.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer())
.addDeserializer(LocalTime.class, new CustomLocalTimeDeserializer())
// Custom serializer to avoid HH:mm:ss.SSS (we don't want millis)
.addSerializer(LocalTime.class, new LocalTimeSerializer()));
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
In all my resources, my Joda date types are properly serialized. However, I wanted to grab the same ObjectMapper to use in a non-Jersey managed context (outside of my resources), so I added #Component to the above class with the intention of auto-wiring it elsewhere. After adding #Component (org.springframework.stereotype.Component), Jersey no longer picks up the ObjectMapper from the resolver and my date serialization goes back to the defaults.
Unless I completely misunderstand the annotations, I don't think giving Spring control of the life-cycle should impede Jersey's ability to pick up my resolver. Additionally worth noting is the fact that when we were on Jersey 1.9, we HAD to have #Component on there or else it would not get picked up. In order to get our upgrade from 1.9 to 2.6 working, I actually had initially removed it, but was hoping to put it back.
From my pom:
Java 1.7
Jackson 2.3.1
Jersey 2.6
Joda 2.1
Spring 4.0.1-RELEASE
I had a similar issue with a similar setup as your one.
While probably there's something wrong in Jersey 2.x Spring integration beahviour, i think you can do the follow:
Declare the object mapper as a Spring bean, so you can inject it via spring where you need it:
#Component
public class ObjectMapperBean extends ObjectMapper {
public ObjectMapperBean() {
super();
// Configuration here...
}
}
Then you write a Jersey context resolver for it:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
#Autowired
private ObjectMapperBean objectMapper;
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
Even if not declared as a component you will get the ObjectMapperBean injected in it.
Hope it helps!
I've been searching how to manage a REST API versions using Spring 3.2.x, but I haven't find anything that is easy to maintain. I'll explain first the problem I have, and then a solution... but I do wonder if I'm re-inventing the wheel here.
I want to manage the version based on the Accept header, and for example if a request has the Accept header application/vnd.company.app-1.1+json, I want spring MVC to forward this to the method that handles this version. And since not all methods in an API change in the same release, I don't want to go to each of my controllers and change anything for a handler that hasn't changed between versions. I also don't want to have the logic to figure out which version to use in the controller themselves (using service locators) as Spring is already discovering which method to call.
So taken an API with versions 1.0, to 1.8 where a handler was introduced in version 1.0 and modified in v1.7, I would like handle this in the following way. Imagine that the code is inside a controller, and that there's some code that is able to extract the version from the header. (The following is invalid in Spring)
#RequestMapping(...)
#VersionRange(1.0,1.6)
#ResponseBody
public Object method1() {
// so something
return object;
}
#RequestMapping(...) //same Request mapping annotation
#VersionRange(1.7)
#ResponseBody
public Object method2() {
// so something
return object;
}
This is not possible in spring as the 2 methods have the same RequestMapping annotation and Spring fails to load. The idea is that the VersionRange annotation can define an open or closed version range. The first method is valid from versions 1.0 to 1.6, while the second for version 1.7 onwards (including the latest version 1.8). I know that this approach breaks if someone decides to pass version 99.99, but that's something I'm OK to live with.
Now, since the above is not possible without a serious rework of how spring works, I was thinking of tinkering with the way handlers matched to requests, in particular to write my own ProducesRequestCondition, and have the version range in there. For example
Code:
#RequestMapping(..., produces = "application/vnd.company.app-[1.0-1.6]+json)
#ResponseBody
public Object method1() {
// so something
return object;
}
#RequestMapping(..., produces = "application/vnd.company.app-[1.7-]+json)
#ResponseBody
public Object method2() {
// so something
return object;
}
In this way, I can have closed or open version ranges defined in the produces part of the annotation. I'm working on this solution now, with the problem that I still had to replace some core Spring MVC classes (RequestMappingInfoHandlerMapping, RequestMappingHandlerMapping and RequestMappingInfo), which I don't like, because it means extra work whenever I decide to upgrade to a newer version of spring.
I would appreciate any thoughts... and especially, any suggestion to do this in a simpler, easier to maintain way.
Edit
Adding a bounty. To get the bounty, please answer the question above without suggesting to have this logic in the controller themselves. Spring already has a lot of logic to select which controller method to call, and I want to piggyback on that.
Edit 2
I've shared the original POC (with some improvements) in github: https://github.com/augusto/restVersioning
Regardless whether versioning can be avoided by doing backwards compatible changes (which might not always possible when you are bound by some corporate guidelines or your API clients are implemented in a buggy way and would break even if they should not) the abstracted requirement is an interesting one:
How can I do a custom request mapping that does arbitrary evaluations of header values from the request without doing the evaluation in the method body?
As described in this SO answer you actually can have the same #RequestMapping and use a different annotation to differentiate during the actual routing that happens during runtime. To do so, you will have to:
Create a new annotation VersionRange.
Implement a RequestCondition<VersionRange>. Since you will have something like a best-match algorithm you will have to check whether methods annotated with other VersionRange values provide a better match for the current request.
Implement a VersionRangeRequestMappingHandlerMapping based on the annotation and request condition (as described in the post How to implement #RequestMapping custom properties
).
Configure spring to evaluate your VersionRangeRequestMappingHandlerMapping before using the default RequestMappingHandlerMapping (e.g. by setting its order to 0).
This wouldn't require any hacky replacements of Spring components but uses the Spring configuration and extension mechanisms so it should work even if you update your Spring version (as long as the new version supports these mechanisms).
I just created a custom solution. I'm using the #ApiVersion annotation in combination with #RequestMapping annotation inside #Controller classes.
Example:
#Controller
#RequestMapping("x")
#ApiVersion(1)
class MyController {
#RequestMapping("a")
void a() {} // maps to /v1/x/a
#RequestMapping("b")
#ApiVersion(2)
void b() {} // maps to /v2/x/b
#RequestMapping("c")
#ApiVersion({1,3})
void c() {} // maps to /v1/x/c
// and to /v3/x/c
}
Implementation:
ApiVersion.java annotation:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ApiVersion {
int[] value();
}
ApiVersionRequestMappingHandlerMapping.java (this is mostly copy and paste from RequestMappingHandlerMapping):
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final String prefix;
public ApiVersionRequestMappingHandlerMapping(String prefix) {
this.prefix = prefix;
}
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if(info == null) return null;
ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if(methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);
} else {
ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
if(typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {
int[] values = annotation.value();
String[] patterns = new String[values.length];
for(int i=0; i<values.length; i++) {
// Build the URL prefix
patterns[i] = prefix+values[i];
}
return new RequestMappingInfo(
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),
new RequestMethodsRequestCondition(),
new ParamsRequestCondition(),
new HeadersRequestCondition(),
new ConsumesRequestCondition(),
new ProducesRequestCondition(),
customCondition);
}
}
Injection into WebMvcConfigurationSupport:
public class WebMvcConfig extends WebMvcConfigurationSupport {
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping("v");
}
}
I have implemented a solution which handles PERFECTLY the problem with rest versioning.
General Speaking there are 3 major approaches for rest versioning:
Path-based approch, in which the client defines the version in URL:
http://localhost:9001/api/v1/user
http://localhost:9001/api/v2/user
Content-Type header, in which the client defines the version in Accept header:
http://localhost:9001/api/v1/user with
Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+json
Custom Header, in which the client defines the version in a custom header.
The problem with the first approach is that if you change the version let's say from v1 -> v2, probably you need to copy-paste the v1 resources that haven't changed to v2 path
The problem with the second approach is that some tools like http://swagger.io/ cannot distinct between operations with same path but different Content-Type (check issue https://github.com/OAI/OpenAPI-Specification/issues/146)
The solution
Since i am working a lot with rest documentation tools, i prefer to use the first approach. My solution handles the problem with the first approach, so you don't need to copy-paste the endpoint to the new version.
Let's say we have v1 and v2 versions for the User controller:
package com.mspapant.example.restVersion.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* The user controller.
*
* #author : Manos Papantonakos on 19/8/2016.
*/
#Controller
#Api(value = "user", description = "Operations about users")
public class UserController {
/**
* Return the user.
*
* #return the user
*/
#ResponseBody
#RequestMapping(method = RequestMethod.GET, value = "/api/v1/user")
#ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
public String getUserV1() {
return "User V1";
}
/**
* Return the user.
*
* #return the user
*/
#ResponseBody
#RequestMapping(method = RequestMethod.GET, value = "/api/v2/user")
#ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"})
public String getUserV2() {
return "User V2";
}
}
The requirement is if i request the v1 for the user resource i have to take the "User V1" repsonse, otherwise if i request the v2, v3 and so on i have to take the "User V2" response.
In order to implement this in spring, we need to override the default RequestMappingHandlerMapping behavior:
package com.mspapant.example.restVersion.conf.mapping;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
#Value("${server.apiContext}")
private String apiContext;
#Value("${server.versionContext}")
private String versionContext;
#Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
HandlerMethod method = super.lookupHandlerMethod(lookupPath, request);
if (method == null && lookupPath.contains(getApiAndVersionContext())) {
String afterAPIURL = lookupPath.substring(lookupPath.indexOf(getApiAndVersionContext()) + getApiAndVersionContext().length());
String version = afterAPIURL.substring(0, afterAPIURL.indexOf("/"));
String path = afterAPIURL.substring(version.length() + 1);
int previousVersion = getPreviousVersion(version);
if (previousVersion != 0) {
lookupPath = getApiAndVersionContext() + previousVersion + "/" + path;
final String lookupFinal = lookupPath;
return lookupHandlerMethod(lookupPath, new HttpServletRequestWrapper(request) {
#Override
public String getRequestURI() {
return lookupFinal;
}
#Override
public String getServletPath() {
return lookupFinal;
}});
}
}
return method;
}
private String getApiAndVersionContext() {
return "/" + apiContext + "/" + versionContext;
}
private int getPreviousVersion(final String version) {
return new Integer(version) - 1 ;
}
}
The implementation reads the version in the URL and asks from spring to resolve the URL .In case this URL does not exists (for example the client requested v3) then we try with v2 and so one until we find the most recent version for the resource.
In order to see the benefits from this implementation, let's say we have two resources: User and Company:
http://localhost:9001/api/v{version}/user
http://localhost:9001/api/v{version}/company
Let's say we made a change in company "contract" that breaks the client. So we implement the http://localhost:9001/api/v2/company and we ask from client to change to v2 instead on v1.
So the new requests from client are:
http://localhost:9001/api/v2/user
http://localhost:9001/api/v2/company
instead of:
http://localhost:9001/api/v1/user
http://localhost:9001/api/v1/company
The best part here is that with this solution the client will get the user information from v1 and company information from v2 without the need to create a new (same) endpoint from user v2!
Rest Documentation
As i said before the reason i select the URL-based versioning approach is that some tools like swagger do not document differently the endpoints with the same URL but different content type. With this solution, both endpoints are displayed since have different URL:
GIT
Solution implementation at:
https://github.com/mspapant/restVersioningExample/
I would still recommend using URL's for versioning because in URLs #RequestMapping supports patterns and path parameters, which format could be specified with regexp.
And to handle client upgrades (which you mentioned in comment) you can use aliases like 'latest'. Or have unversioned version of api which uses latest version (yeah).
Also using path parameters you can implement any complex version handling logic, and if you already want to have ranges, you very well might want something more soon enough.
Here is a couple of examples:
#RequestMapping({
"/**/public_api/1.1/method",
"/**/public_api/1.2/method",
})
public void method1(){
}
#RequestMapping({
"/**/public_api/1.3/method"
"/**/public_api/latest/method"
"/**/public_api/method"
})
public void method2(){
}
#RequestMapping({
"/**/public_api/1.4/method"
"/**/public_api/beta/method"
})
public void method2(){
}
//handles all 1.* requests
#RequestMapping({
"/**/public_api/{version:1\\.\\d+}/method"
})
public void methodManual1(#PathVariable("version") String version){
}
//handles 1.0-1.6 range, but somewhat ugly
#RequestMapping({
"/**/public_api/{version:1\\.[0123456]?}/method"
})
public void methodManual1(#PathVariable("version") String version){
}
//fully manual version handling
#RequestMapping({
"/**/public_api/{version}/method"
})
public void methodManual2(#PathVariable("version") String version){
int[] versionParts = getVersionParts(version);
//manual handling of versions
}
public int[] getVersionParts(String version){
try{
String[] versionParts = version.split("\\.");
int[] result = new int[versionParts.length];
for(int i=0;i<versionParts.length;i++){
result[i] = Integer.parseInt(versionParts[i]);
}
return result;
}catch (Exception ex) {
return null;
}
}
Based on the last approach you can actually implement something like what you want.
For example you can have a controller that contains only method stabs with version handling.
In that handling you look (using reflection/AOP/code generation libraries) in some spring service/component or in the same class for method with the same name/signature and required #VersionRange and invoke it passing all parameters.
The #RequestMapping annotation supports a headers element that allows you to narrow the matching requests. In particular you can use the Accept header here.
#RequestMapping(headers = {
"Accept=application/vnd.company.app-1.0+json",
"Accept=application/vnd.company.app-1.1+json"
})
This isn't exactly what you're describing, since it doesn't directly handle ranges, but the element does support the * wildcard as well as !=. So at least you could get away with using a wildcard for cases where all versions support the endpoint in question, or even all minor versions of a given major version (e.g. 1.*).
I don't think I've actually used this element before (if I have I don't remember), so I'm just going off the documentation at
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html
I already tried to version my API using the URI Versioning, like:
/api/v1/orders
/api/v2/orders
But there are some challenges when trying to make this work: how organize your code with different versions? How manage two (or more) versions at the same time? What's the impact when removing some version?
The best alternative that I found was not version the entire API, but control the version on each endpoint. This pattern is called Versioning using Accept header or Versioning through content negotiation:
This approach allows us to version a single resource representation
instead of versioning the entire API which gives us a more granular
control over versioning. It also creates a smaller footprint in the
code base as we don’t have to fork the entire application when
creating a new version. Another advantage of this approach is that it
doesn’t require implementing URI routing rules introduced by
versioning through the URI path.
Implementation on Spring
First, you create a Controller with a produces attribute, that will applied by default on each endpoint inside the same class.
#RestController
#RequestMapping(value = "/api/orders/", produces = "application/vnd.company.etc.v1+json")
public class OrderController {
}
After that, we can imagine a possible scenario where you have two versions (v1 and v2) of an endpoint for "create an order":
#Deprecated
#PostMapping
public ResponseEntity<OrderResponse> createV1(
#RequestBody OrderRequest orderRequest) {
OrderResponse response = createOrderService.createOrder(orderRequest);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
#PostMapping(
produces = "application/vnd.company.etc.v2+json",
consumes = "application/vnd.company.etc.v2+json")
public ResponseEntity<OrderResponseV2> createV2(
#RequestBody OrderRequestV2 orderRequest) {
OrderResponse response = createOrderService.createOrder(orderRequest);
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
Done! Just call each endpoint using the desired Http Header version:
Content-Type: application/vnd.company.etc.v1+json
Or, to call the v2:
Content-Type: application/vnd.company.etc.v2+json
About your worries:
And since not all methods in an API change in the same release, I
don't want to go to each of my controllers and change anything for a
handler that hasn't changed between versions
As explained, this strategy maintains each Controller and endpoint with his actual version. You only modify the endpoint that have modifications and needs a new version.
And the Swagger?
Setup the Swagger with different versions is also very easy using this strategy. See this answer to more details.
What about just using inheritance to model versioning? That is what I'm using in my project and it requires no special spring configuration and gets me exactly what I want.
#RestController
#RequestMapping(value = "/test/1")
#Deprecated
public class Test1 {
...Fields Getters Setters...
#RequestMapping(method = RequestMethod.GET)
#Deprecated
public Test getTest(Long id) {
return serviceClass.getTestById(id);
}
#RequestMapping(method = RequestMethod.PUT)
public Test getTest(Test test) {
return serviceClass.updateTest(test);
}
}
#RestController
#RequestMapping(value = "/test/2")
public class Test2 extends Test1 {
...Fields Getters Setters...
#Override
#RequestMapping(method = RequestMethod.GET)
public Test getTest(Long id) {
return serviceClass.getAUpdated(id);
}
#RequestMapping(method = RequestMethod.DELETE)
public Test deleteTest(Long id) {
return serviceClass.deleteTestById(id);
}
}
This set up allows for little duplication of code and the ability to overwrite methods into new versions of the api with little work. It also saves the need to complicate your source code with version switching logic. If you don't code an endpoint in a version it will grab the previous version by default.
Compared to what others are doing this seems way easier. Is there something I'm missing?
In produces you can have negation. So for method1 say produces="!...1.7" and in method2 have the positive.
The produces is also an array so you for method1 you can say produces={"...1.6","!...1.7","...1.8"} etc (accept all except 1.7)
Ofcourse not as ideal as ranges that you have in mind but I think easier to maintain than other custom stuff if this is something uncommon in your system. Good luck!
You can use AOP, around interception
Consider having a request mapping which receives all the /**/public_api/* and in this method do nothing;
#RequestMapping({
"/**/public_api/*"
})
public void method2(Model model){
}
After
#Override
public void around(Method method, Object[] args, Object target)
throws Throwable {
// look for the requested version from model parameter, call it desired range
// check the target object for #VersionRange annotation with reflection and acquire version ranges, call the function if it is in the desired range
}
The only constraint is that all has to be in the same controller.
For AOP configuration have a look at http://www.mkyong.com/spring/spring-aop-examples-advice/