How to override #RequestMapping in another controller? - java

I need to extend an existing controller and add some functionality to it. But as a project requirement I can't touch in the original controller, the problem is that this controller have an #RequestMapping annotation on it. So my question is how can I make requests to /someUrl go to my new controller instead of the old one.
here is a example just to clarify what I'm talking about:
Original controller:
#Controller
public class HelloWorldController {
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World!");
return "helloWorld";
}
}
new Controller:
#Controller
public class MyHelloWorldController {
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World from my new controller");
// a lot of new logic
return "helloWorld";
}
}
how can I override the original mapping without editing HelloWorldController?

Url mapping as annotation can not be overridden. You will get an error if two or more Controllers are configured with the same request url and request method.
What you can do is to extend the request mapping:
#Controller
public class MyHelloWorldController {
#RequestMapping("/helloWorld", params = { "type=42" })
public String helloWorld(Model model) {
model.addAttribute("message", "Hello World from my new controller");
return "helloWorld";
}
}
Example: Now if you call yourhost/helloWorld?type=42 MyHelloWorldController will response the request
By the way.
Controller should not be a dynamic content provider. You need a #Service instance. So you can implement Controller once and use multiple Service implementation. This is the main idea of Spring MVC and DI
#Controller
public class HelloWorldController {
#Autowired
private MessageService _messageService; // -> new MessageServiceImpl1() or new MessageServiceImpl2() ...
#RequestMapping("/helloWorld")
public String helloWorld(Model model) {
model.addAttribute("message", messageService.getMessage());
return "helloWorld";
}
}

Here is another workaround, that may or may not be dangerous.
Create the below class "MyRequestMappingHandler", then wire it up in your MvcConfig
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new MyRequestMappingHandler();
}
RequestMappingHandlerMapping: * THIS IS NOT PRODUCTION CODE - UP TO YOU *
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class MyRequestMappingHandler extends RequestMappingHandlerMapping {
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingForMethod = super.getMappingForMethod(method, handlerType);
// Check if this class extends a super. and that super is annotated with #Controller.
Class superClass = handlerType.getSuperclass();
if (superClass.isAnnotationPresent(Controller.class)) {
// We have a super class controller.
if (handlerType.isAnnotationPresent(Primary.class)) {
// We have a #Primary on the child.
return mappingForMethod;
}
} else {
// We do not have a super class, therefore we need to look for other implementations of this class.
Map<String, Object> controllerBeans = getApplicationContext().getBeansWithAnnotation(Controller.class);
List<Map.Entry<String, Object>> classesExtendingHandler = controllerBeans.entrySet().stream().filter(e ->
AopUtils.getTargetClass(e.getValue()).getSuperclass().getName().equalsIgnoreCase(handlerType
.getName()) &&
!AopUtils.getTargetClass(e.getValue()).getName().equalsIgnoreCase(handlerType.getName()))
.collect(Collectors.toList());
if (classesExtendingHandler == null || classesExtendingHandler.isEmpty()) {
// No classes extend this handler, therefore it is the only one.
return mappingForMethod;
} else {
// Classes extend this handler,
// If this handler is marked with #Primary and no others are then return info;
List<Map.Entry<String, Object>> classesWithPrimary = classesExtendingHandler
.stream()
.filter(e -> e.getValue().getClass().isAnnotationPresent(Primary.class) &&
!AopUtils.getTargetClass(e.getValue().getClass()).getName().equalsIgnoreCase
(handlerType.getName()))
.collect(Collectors.toList());
if (classesWithPrimary == null || classesWithPrimary.isEmpty()) {
// No classes are marked with primary.
return null;
} else {
// One or more classes are marked with #Primary,
if (classesWithPrimary.size() == 1 && AopUtils.getTargetClass(classesWithPrimary.get(0).getValue
()).getClass().getName().equalsIgnoreCase(handlerType.getName())) {
// We have only one and it is this one, return it.
return mappingForMethod;
} else if (classesWithPrimary.size() == 1 && !AopUtils.getTargetClass(classesWithPrimary.get(0)
.getValue()).getClass().getName().equalsIgnoreCase(handlerType.getName())) {
// Nothing.
} else {
// nothing.
}
}
}
}
// If it does, and it is marked with #Primary, then return info.
// else If it does not extend a super with #Controller and there are no children, then return info;
return null;
}
}
What this allows you to do is, extend a #Controller class, and mark it with #Primary, and override a method on that class, your new class will now be loaded up when spring starts up instead of blowing up with "multiple beans / request mappings etc"
Example of "super" Controller :
#Controller
public class Foobar {
#RequestMapping(method = "GET")
private String index() {
return "view";
}
}
Example of implementation :
#Primary
#Controller
public class MyFoobar extends Foobar {
#Override
private String index() {
return "myView";
}
}

Each mapping must be unique.. There is no way to overrule an existing #RequestMapping.
BUT You can always do some workarounds:
Use a param in the request like this will create a new #RequestMapping that will differ from the existing one.
#RequestMapping("/helloWorld/{someDataId}", method = RequestMethod.GET)
public String helloWorld(#PathVariable("someDataId") final long id, Model model) {
/* your code here */
}
Or creating another #Controller extending the existing one:
public class YourController extends BaseController {
#Override
#RequestMapping("/helloWorld")
public void renderDashboard(Model model){
// Call to default functionallity (if you want...)
super.renderDashboard(patientId, map);
}
}

You can dynamically (on application startup) deregister the existing handler methods from the RequestMappingHandlerMapping, and register your (new) handler method instead.
This could be done as follows:
class ApplicationConfig {
#Bean
NewController newController() {
return new NewController();
}
#Autowired
public void registerOverriddenControllerEndpoint(final RequestMappingHandlerMapping handlerMapping,
final NewController controller) throws NoSuchMethodException {
final RequestMappingInfo mapping = RequestMappingInfo.paths("path/to/be/overridden")
.methods(RequestMethod.GET) // or any other request method
.build();
handlerMapping.unregisterMapping(mapping);
Class[] argTypes = new Class[]{/* The parameter types needed for the 'methodThatHandlesTheEndpoint' method */};
handlerMapping.registerMapping(mapping, controller, NewController.class.getMethod("methodThatHandlesTheEndpoint", argTypes));
}
}
This means, that I have now two methods with the same mapping:
class ExistingController {
// This will be now ignored
#GetMapping("path/to/be/overridden")
public ResponseEntity<Void> methodThatHandlesTheEndpoint() {
}
}
and
class NewController {
// This will be now the main handler
#GetMapping("path/to/be/overridden")
public ResponseEntity<Void> methodThatHandlesTheEndpoint() {
}
}

Related

How to dynamically disable specific API in spring?

I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?
You could use AOP to do something like that.
Create your own annotation...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Maybe { }
and corresponding aspect...
#Aspect
public class MaybeAspect {
#Pointcut("#annotation(com.example.Maybe)")
public void callMeMaybe() {}
#Around("callMeMaybe()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// do your logic here..
if(DISABLE_FOO) {
// do nothing ? throw exception?
// return null;
throw new IllegalStateException();
} else {
// process the request normally
return joinPoint.proceed();
}
}
}
I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.
Here is the 2 ways disabling by spring profile or JVM properties.
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
// Using profile
if (env.acceptsProfiles("staging")) {
response.setStatus(404);
return "";
}
// Using JVM options
if("true".equals(System.getProperty("DISABLE_FOO"))) {
response.setStatus(404);
return "";
}
return "";
}
}
If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/
Using Conditional components
This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation
#Conditional(ConditionalController.class)
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
return "";
}
public static class ConditionalController implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
}
}
}
You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:
#Profile("DISABLED_FLAG")
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#Profile("ENABLED_FLAG")
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html
I did it as follows :
#Retention(RUNTIME)
#Target(ElementType.METHOD)
public #interface DisableApiControl {
}
This class is my customization statement. After could use AOP :
for AbstractBaseServiceImpl :
public abstract class AbstractBaseServiceImpl {
private static boolean disableCheck = false;
public void setDisableChecker(boolean checkParameter) {
disableCheck = checkParameter;
}
public boolean getDisableChecker() {
return disableCheck;
}
}
NOTE : The above class has been prepared to provide a dynamic structure.
#Aspect
#Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {
#Autowired
private HttpServletResponse httpServletResponse;
#Pointcut(" #annotation(disableMe)")
protected void disabledMethods(DisableApiControl disableMe) {
// comment line
}
#Around("disabledMethods(disableMe)")
public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
if (getDisableChecker()) {
httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
return null;
} else {
return joinPoint.proceed();
}
}
}
checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.
#GetMapping("/map")
#DisableApiControl
public List<?> stateMachineFindMap() {
return new ArrayList<>;
}

Stop Spring MVC annotations processing

I need to stop processing of Spring MVC annotations on interface, but bean for this interface should be created.
e.g. I have shared Api interface with MVC REST annotations, Controller implements this Api. In other project I create REST client based on interface (by processing annotations). But when I create client, Spring sees interface as return type and process annotations inside it. So, I need to stop annotations processing when I create REST client, but for controller annotations should work (now they work OK).
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
}
I solved it by creating new annotation which is used to mark API interface and overriding boolean isHandler(Class<?> beanType) method of org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping. This method originally checks whether class (or any interface that class implements) is annotated with Controller or RequestMapping annotations. I added extra check that looks up for my BackEndApiInterface annotation and if it is found then return false. Here is the code:
#Retention(RetentionPolicy.RUNTIME)
public #interface BackEndApiInterface {
}
#BackEndApiInterface
#RequestMapping("/resource1")
public interface Api {
#RequestMapping(method = RequestMethod.POST)
Resource1 getResource1();
}
#RestController
public class Controller implements Api {
#Override
public Resource1 getResource1() {
return null;
}
}
#Configuration
public class Config {
#Bean
public Api api() {
return RestClientFactory.createRestClientBasedOnAnnotations(Api.class);
}
#Bean
public static RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
#Override
protected boolean isHandler(Class<?> beanType) {
if (AnnotationUtils.findAnnotation(beanType, BackEndApiInterface.class) != null) {
return false;
}
return super.isHandler(beanType);
}
};
}
}
you could move the annotations to the implementation and just keep the interface as pure java.

Jersey/Jax-RS: how to filter resource and sub-resources

In Jersey 2, how can I bind a filter to all the method of a Resource as well as to all the methods of its sub-resources?
For example, if I have the following 2 resources:
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.model.Resource;
#Path("/myresource/{id: \\d+}")
#Produces(MediaType.APPLICATION_JSON)
#Singleton
class RootResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response get(#PathParam("id") Long id) {
return Response.ok().build();
}
#Path("/sub")
public Resource getSubResource() {
return Resource.from(SubResource.class);
}
}
#Produces(MediaType.APPLICATION_JSON)
#Singleton
class SubResource {
#GET
#Path("/{subid: \\d+}")
public Response get(#PathParam("id") Long id, #PathParam("subid") Long subid) {
return Response.ok().build();
}
}
I would like to filter RootResource.get(Long) and SubResource.get(Long, Long). But if I have other resources, those should not be filtered.
Using the DynamicFeature, we only have info on the Class and Method.
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.FeatureContext;
public class MyFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
// Here how can I find out that SubResource is actually a sub-resource of RootResource
}
}
The idea is that I want to be able to filter out all calls for a certain set of id's (the set of id's is dynamic), with something more or less like this:
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
public class MyFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
for(Object resource:requestContext.getUriInfo().getMatchedResources()) {
if(resource instanceof RootResource) {
Long id = Long.valueOf(requestContext.getUriInfo().getPathParameters().getFirst("id"));
// ...
}
}
}
}
but I would like to avoid having to search for the matched resources. Is this possible?
I'm not 100% sure I understand the problem, but it seems that you want to want to limit which resources should go through the filter. For that you can simply use Name Binding.
Basic Steps:
Create a #NameBinding annotation
#NameBinding
#Target({METHOD, TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Filtered {
}
Annotate the filter
#Filtered
#Provider
public class MyFilter implements ContainerRequestFilter {
Annotate whatever root resources, resource methods, sub resource classes you want to be filtered
UPDATE
OK so after some playing around, I came up with a couple solutions.. netiher of which are pretty, but it gets the job done.
Keep in mind that configure in the DynamicFeature is called for each resource(method) we have.
Algorithm 1:
Get the method being checked and get its declaring class (in the case of a method in a sub resource, the declaring class will be the sub resource class)
Class<?> possibleSubResource =
resourceInfo.getResourceMethod().getDeclaringClass();
Build a temporary Resource from your root resource
Resource resource = Resource.from(SomeResource.class);
Iterate its child resources, checking if it's a resource locator
for (Resource childResource : resource.getChildResources()) {
if (childResource.getResourceLocator() != null) {
If is is resource locator get the return type.
ResourceMethod sub = childResource.getResourceLocator();
Class responseClass = sub.getInvocable().getRawResponseType();
Then check if the response type from step 4 == the declaring class from step 1.
if (responseClass == possibleSubResource) {
context.register(SomeFilter.class);
}
For the above to work, you actually need to return the sub resource type from the locator method, instead of a Resource. (You can try and make it work with Resource, but I haven't been able to figure it out)
#Path("{id}")
public SomeSubResource getSubResource() {
return new SomeSubResource();
}
Here is the full code that works (not battle tested :-)
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
Class<?> resourceClass = resourceInfo.getResourceClass();
if (resourceClass == SomeResource.class) {
context.register(SomeFilter.class);
}
Class<?> possibleSubResource = resourceInfo.getResourceMethod().getDeclaringClass();
Resource resource = Resource.from(SomeResource.class);
for (Resource childResource : resource.getChildResources()) {
if (childResource.getResourceLocator() != null) {
ResourceMethod sub = childResource.getResourceLocator();
Class responseClass = sub.getInvocable().getRawResponseType();
if (responseClass == possibleSubResource) {
context.register(SomeFilter.class);
}
}
}
}
Algorithm 2:
For this to work, we are going based off the assumption that what defines a Sub Resource is that it is annotation with #Path and has no Http method annotation
Get the method being checked and get its declaring class (in the case of a method in a sub resource, the declaring class will be the sub resource class)
Class<?> possibleSubResource =
resourceInfo.getResourceMethod().getDeclaringClass();
Iterate through the Methods in the root resource class
for (Method method : SomeResource.class.getDeclaredMethods()) {
Check if the method has an Http method annotation
boolean isHttpPresent = false;
for (Class annot : Arrays.asList(GET.class, POST.class, PUT.class, DELETE.class)) {
if (method.isAnnotationPresent(annot)) {
isHttpPresent = true;
break;
}
}
Check if the method has the #Path annotation. If it does, and it has not Http method annotations, then we register the filter
if (method.isAnnotationPresent(Path.class) && !isHttpPresent) {
Class subResourceClass = method.getReturnType();
if (subResourceClass == possibleSubResource) {
context.register(SomeFilter.class);
}
}
Here is the full code
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
Class<?> resourceClass = resourceInfo.getResourceClass();
if (resourceClass == SomeResource.class) {
context.register(SomeFilter.class);
}
Class<?> possibleSubResource = resourceInfo.getResourceMethod().getDeclaringClass();
for (Method method : SomeResource.class.getDeclaredMethods()) {
boolean isHttpPresent = false;
for(Class annot : Arrays.asList(GET.class,POST.class,PUT.class, DELETE.class)){
if (method.isAnnotationPresent(annot)) {
isHttpPresent = true;
break;
}
}
if(method.isAnnotationPresent(Path.class) && !isHttpPresent){
Class subResourceClass = method.getReturnType();
if (subResourceClass == possibleSubResource) {
context.register(SomeFilter.class);
}
}
}
}
Again, neither of these solutions are battled tested, but work for the few cases I have tried. Personally, I'd just go with the name binding, but maybe this is an issue you can raise with the Jersey team. This (automatic registration of sub resource, when the root resources are registered) does seem like something that should work out the box, or at least be able to be configured.
I had a similar need:
I wanted an annotation to specifically filter resource methods in order to achieve
something like this:
#Path("/api/sample")
#Produces(MediaType.APPLICATION_JSON)
public class SampleResource {
#Path("/filtered")
#GET
#Sample(value = "a sample value")
public Hello filtered() {
return new Hello("filtered hello");
}
#Path("/nonfiltered")
#GET
public Hello raw() {
return new Hello("raw hello");
}
}
My annotation being:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Sample {
String value() default "";
}
I ended up using a DynamicFeature to register a Filter on Resource
#Provider
public class SampleFeature implements DynamicFeature {
private SampleFilter sampleFilter;
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if (resourceInfo.getResourceMethod().getAnnotation(Sample.class) != null) {
if (sampleFilter == null) {
this.sampleFilter = new SampleFilter();
}
context.register(sampleFilter);
}
}
}
the tricky thing was to find out how I could fetch the annotation value within my filter, hence to find out about ExtendedUriInfo, see below:
public class SampleFilter implements ContainerRequestFilter {
public SampleFilter() {
}
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
String sampleValue = this.getAnnotation(containerRequestContext).value();
// do some filtering based on the Sample Value
}
private Sample getAnnotation(ContainerRequestContext requestContext) {
ResourceMethod method = ((ExtendedUriInfo) (requestContext.getUriInfo()))
.getMatchedResourceMethod();
Method invokedMethod = method.getInvocable().getHandlingMethod();
return invokedMethod.getAnnotation(Sample.class);
}
}

Intercept all responses in Play 2.1

Is there a way to intercept all HTTP responses in using Play Framework 2.1?
This is what I have in my Global.java file to intercept all requests, but I'm also looking to intercept responses:
import java.lang.reflect.Method;
import play.GlobalSettings;
import play.mvc.*;
import play.mvc.Http.*;
import views.html.*;
public class Global extends GlobalSettings {
private static BasicAuthHandler AUTH;
#SuppressWarnings("rawtypes")
#Override
public Action onRequest(Request request, Method actionMethod) {
if ( ... ) {
return new Action.Simple() {
#Override
public Result call(Context ctx) throws Throwable {
return unauthorized();
}
};
}
return super.onRequest(request, actionMethod);
}
}
I've read the documentation on manipulating the response but it only describes how to do it for each result individually.
TransactionalAction is an example of request/response interceptor. It extends Action and provides Transactional annotation which targets controller type or method.
Example of controller method annotated with action:
#Transactional
public static Result ok(){
return ok();
}
More details.
An example of action logging responses (mind, actions which do not provide annotations like Transactional, extend Action.Simple):
public class LogAction extends Action.Simple {
#Override
public F.Promise<Result> call(Http.Context ctx) throws Throwable {
F.Promise<Result> call = delegate.call(ctx);
return call.map(r -> {
String responseBody = new String(JavaResultExtractor.getBody(r, 0L));
Logger.info(responseBody);
return r;
});
}
}
Usage, method definition:
#With(LogAction.class)
public static Result ok(){
return ok();
}
Usage, class definition - all methods intercepted:
#With(LogAction.class)
public class BaseController extends Controller {
....
}
You can go one step forward, if you dont like #With annotation. Define custom annotation yourself:
#With({ LogAction.class })
#Target({ ElementType.TYPE, ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface Log {
}
and use it this way:
#Log
public static Result ok(){
return ok();
}
If your custom annotation accepts parameters, change LogAction definition this way:
public class LogAction extends Action<Log> {
// use configuration object to access your custom annotation configuration
}

How do I design a generic Response builder / RESTful Web Service using Spring MVC?

Trying to build a RESTful web service using Spring MVC.
The controller should return specific Java types, but the response body must be a generic envelope. How can this be done?
The following sections of code are what I have so far:
Controller method:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyController {
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Response envelope:
public class Response<T> {
private String message;
private T responseBody;
}
ServiceDetails code:
public class ServiceDetails {
private String serviceName;
public ServiceDetails(String serviceName) {
this.serviceName = serviceName;
}
}
Intended final response to clients should appear as:
{
"message" : "Operation OK"
"responseBody" : {
"serviceName" : "MyService"
}
}
What you can do is having a MyRestController just wrapping the result in a Response like this:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyRestController {
#Autowired
private MyController myController;
#RequestMapping(value = "/details")
public #ResponseBody Response<ServiceDetails> getServiceDetails() {
return new Response(myController.getServiceDetails(),"Operation OK");
}
}
This solution keep your original MyController independant from your REST code. It seems you need to include Jackson in your classpath so that Spring will auto-magically serialize to JSON (see this for details)
EDIT
It seems you need something more generic... so here is a suggestion.
#Controller
#RequestMapping(value = "/mycontroller")
public class MyGenericRestController {
#Autowired
private MyController myController;
//this will match all "/myController/*"
#RequestMapping(value = "/{operation}")
public #ResponseBody Response getGenericOperation(String #PathVariable operation) {
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try{
responseBody = operationToInvoke.invoke(myController);
}catch(Exception e){
e.printStackTrace();
return new Response(null,"operation failed");
}
return new Response(responseBody ,"Operation OK");
}
private Method findMethodWithRequestMapping(String operation){
//TODO
//This method will use reflection to find a method annotated
//#RequestMapping(value=<operation>)
//in myController
return ...
}
}
And keep your original "myController" almost as it was:
#Controller
public class MyController {
//this method is not expected to be called directly by spring MVC
#RequestMapping(value = "/details")
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Major issue with this : the #RequestMapping in MyController need probably to be replaced by some custom annotation (and adapt findMethodWithRequestMapping to perform introspection on this custom annotation).
By default, Spring MVC uses org.springframework.http.converter.json.MappingJacksonHttpMessageConverter to serialize/deserialize JSON through Jackson.
I'm not sure if it's a great idea, but one way of solving your problem is to extend this class, and override the writeInternal method:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
}
}
If you're using XML configuration, you could enable the custom converter like this:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
Try the below solution.
Create a separate class such ResponseEnvelop. It must implement ResponseBodyAdvice interface.
Annotate the above class with #ControllerAdvice
Autowire HttpServletRequest
Override methods according to your requirement. Take reference from below.
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (httpServletRequest.getRequestURI().startsWith("/api")) {
return true;
}
return false;
}
#Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.OK.value()
|| ((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.CREATED.value()) {
return new EntityResponse(Constants.SUCCESS, body);
}
return body;
}

Categories

Resources