I have the following combined custom annotation with Springs #PreAuthorize annotation,
#RequestMapping(
produces = MimeTypeUtils.APPLICATION_JSON_VALUE + ";charset=UTF-8",
method = RequestMethod.GET)
#ResponseBody
#PreAuthorize(value = "hasRole('permitAll()')")
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
#AliasFor(annotation = PreAuthorize.class, attribute = "value")
String authorize() default "permitAll()";
}
And I've got the following client using it
#Get(value = "/users", authorize = "hasRole('ROLE_GET_USERS')")
public List<User> retrieveUsers() {
// body
}
As you can see the purpose is to allow clients of #Get annotation to override the #PreAuthorize so that they can provide the role they require for.
I din't have so far any problem using #AliasFor, even in this example #RequestMapping is working, but unfortunately it does not override the value of #PreAuthorize and everyone can still access the resources as the default value is permitAll().
I wonder first of all why this does not work, and second if it is possible to make it work?
Related
I create a custom annotation and HandlerInterceptorAdapter that will just get memberNo and print it out.
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface MyCustomAnnotation
{
String memberNo();
}
And on Controller something like this:
#MyCustomAnnotation(memberNo = "${someBodyObject.memberNo}")
#RequestMapping(value = "/test/", method = RequestMethod.GET)
public String test(#RequestBody SomeBodyObject someBodyObject) {
System.out.println("--- TEST ---");
return "-- FINISHED ---";
}
Request body SomeBodyObject have one filed and it's memberNo.
How can I inject that memberNo from RequestBody to HandlerInterceptorAdapter?
And is it possible to user Spring SpEL or something else to get data from Body and send to AnnotationResolver?
Or maybe there is some other way to do this?
I try like this and response is: ${token.memberNo} as a String
That's not possible.
Annotations require constant values and a method parameter is dynamic.
I am using Swagger version 2.4.0 and Spring Boot 2.0.4.RELEASE and have an application with several API endpoints and with default Swagger configuration having default produces header value set to application/json.
SwaggerConfig.java
#Configuration
#EnableSwagger2
public class SwaggerConfig {
private static final Set<String> DEFAULT_PRODUCES_AND_CONSUMES = ImmutableSet.of(
"application/json"
);
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.produces(DEFAULT_PRODUCES_AND_CONSUMES)
.consumes(DEFAULT_PRODUCES_AND_CONSUMES);
}
}
And with API endpoint set up being.
ApiEndpoint.java
#Consumes(MediaType.APPLICATION_JSON)
#Api(value = "/transform", protocols = "http, https")
#RequestMapping(value = "/transform")
public interface ApiEndpoint {
#POST
#RequestMapping(value = "/text", method = RequestMethod.POST)
#ApiOperation(value = "Transform to plain text", produces = MediaType.TEXT_PLAIN)
#CrossOrigin
String transformToText(#RequestBody TransformRequest transformRequest) throws Exception;
}
Now I want this endpoint to produce response with content type being only plain text, but SwaggerConfig adds application/json option as default one. So in order to correctly use this endpoint I would need to change Response Content Type from application/json to text/plain every time, which would get annoying pretty quick considering that this endpoint is used for testing. Is there a way to override SwaggerConfig or to add a parameter so text/plain is the only option or at least to set the text/plain as the default option only for this one endpoint?
you just have to define the response content type in your requestMapping annotation.
That is,
#RequestMapping(value = "/text", method = RequestMethod.POST)
will be replaced by,
#RequestMapping(value = "/text", method = RequestMethod.POST, produces="text/plain")
Means, you have to define in requestMapping that what type of content this mapping will going to return.
Note : Will be good practice if you use
#PostMapping(value = "/text", produces="text/plain")
Hope, the solution will work fine.
just specifying type in order in the endpoint annotation, example:
atribute produces receive an array, so you can put more than one type
#PostMapping(value = "/text", produces = { MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE })
I advise removing produces in #Bean, as its API contains endpoint that did not always follow the standard idea of "application/json"
I am trying to use the Meta-annotation of spring using the aliasFor annotation to create a custom annotation for the springs RequestParam
Simply 'extend/replace'
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface RequestParam {
#AliasFor("name")
String value() default "";
----
}
with my annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface QueryParam {
#AliasFor(annotation = RequestParam.class, attribute = "name")
String name() default "";
#AliasFor(annotation = RequestParam.class, attribute = "required")
boolean required() default false;
#AliasFor(annotation = RequestParam.class, attribute = "defaultValue")
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
This way it throws the Exception
org.springframework.core.annotation.AnnotationConfigurationException: #AliasFor declaration on attribute [name] in annotation [package.QueryParam] declares an alias for attribute [name] in meta-annotation [org.springframework.web.bind.annotation.RequestParam] which is not meta-present.
Problem is that without the RequestParam annotated on the QueryParam this doesn't work. And it is not possible to put the RequestParam as it PARAMETER targeted.
#RequestParam <--This is not possible.
public #interface QueryParam
So is there another way to achieve this ?
Basically what you want to achieve is not possible now, at least for the Spring v 4.3.3 There are main two problems, the first one is the fact that annotations like #RequestParam are declared with #Target(ElementType.PARAMETER) which make it impossible to be used as part of meta annotations. Furthermore, Spring MVC looks up annotations on method parameters using org.springframework.core.MethodParameter.getParameterAnnotations() which does not support meta-annotations or composed annotations. But if you really need some customizations there you can use HandlerMethodArgumentResolver instead of meta annotations.
So you code will look something like
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface QueryParam {
String name() default "";
boolean required() default false;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}
Then using the HandlerMethodArgumentResolver add the custom logic which you need.
public class QueryParamResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(QueryParam.class) != null;
}
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
QueryParam attr = parameter.getParameterAnnotation(QueryParam.class);
// here you can use any logic which you need
return webRequest.getParameter(attr.value());
}
}
Then we need to register our HandlerMethodArgumentResolver
#Configuration
#EnableWebMvc
public class Config extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new QueryParamResolver());
}
}
And last lets use our custom annotation
#GetMapping("/test")
public String test(#QueryParam("foo") String foo){
// something here
}
i had same problem and I entered the wrong package for GetAmpping. I must import «import org.springframework.web.bind.annotation.GetMapping» .
With multiple Spring controllers that consume and produce application/json, my code is littered with long annotations like:
#RequestMapping(value = "/foo", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
Is there a way to produce a "composite/inherited/aggregated" annotation with default values for consumes and produces, such that I could instead write something like:
#JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
How do we define something like #JSONRequestMapping above? Notice the value and method passed in just like in #RequestMapping, also good to be able to pass in consumes or produces if the default isn't suitable.
I need to control what I'm returning. I want the produces/consumes annotation-methods so that I get the appropriate Content-Type headers.
As of Spring 4.2.x, you can create custom mapping annotations, using #RequestMapping as a meta-annotation. So:
Is there a way to produce a "composite/inherited/aggregated"
annotation with default values for consumes and produces, such that I
could instead write something like:
#JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
Yes, there is such a way. You can create a meta annotation like following:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping(consumes = "application/json", produces = "application/json")
public #interface JsonRequestMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "method")
RequestMethod[] method() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "params")
String[] params() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "headers")
String[] headers() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "consumes")
String[] consumes() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "produces")
String[] produces() default {};
}
Then you can use the default settings or even override them as you want:
#JsonRequestMapping(method = POST)
public String defaultSettings() {
return "Default settings";
}
#JsonRequestMapping(value = "/override", method = PUT, produces = "text/plain")
public String overrideSome(#RequestBody String json) {
return json;
}
You can read more about AliasFor in spring's javadoc and github wiki.
The simple answer to your question is that there is no Annotation-Inheritance in Java. However, there is a way to use the Spring annotations in a way that I think will help solve your problem.
#RequestMapping is supported at both the type level and at the method level.
When you put #RequestMapping at the type level, most of the attributes are 'inherited' for each method in that class. This is mentioned in the Spring reference documentation. Look at the api docs for details on how each attribute is handled when adding #RequestMapping to a type. I've summarized this for each attribute below:
name: Value at Type level is concatenated with value at method level using '#' as a separator.
value: Value at Type level is inherited by method.
path: Value at Type level is inherited by method.
method: Value at Type level is inherited by method.
params: Value at Type level is inherited by method.
headers: Value at Type level is inherited by method.
consumes: Value at Type level is overridden by method.
produces: Value at Type level is overridden by method.
Here is a brief example Controller that showcases how you could use this:
package com.example;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping(path = "/",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
method = {RequestMethod.GET, RequestMethod.POST})
public class JsonProducingEndpoint {
private FooService fooService;
#RequestMapping(path = "/foo", method = RequestMethod.POST)
public String postAFoo(#RequestBody ThisIsAFoo theFoo) {
fooService.saveTheFoo(theFoo);
return "http://myservice.com/foo/1";
}
#RequestMapping(path = "/foo/{id}", method = RequestMethod.GET)
public ThisIsAFoo getAFoo(#PathVariable String id) {
ThisIsAFoo foo = fooService.getAFoo(id);
return foo;
}
#RequestMapping(path = "/foo/{id}", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
public ThisIsAFooXML getAFooXml(#PathVariable String id) {
ThisIsAFooXML foo = fooService.getAFoo(id);
return foo;
}
}
You shouldn't need to configure the consumes or produces attribute at all. Spring will automatically serve JSON based on the following factors.
The accepts header of the request is application/json
#ResponseBody annotated method
Jackson library on classpath
You should also follow Wim's suggestion and define your controller with the #RestController annotation. This will save you from annotating each request method with #ResponseBody
Another benefit of this approach would be if a client wants XML instead of JSON, they would get it. They would just need to specify xml in the accepts header.
You can use the #RestController instead of #Controller annotation.
There are 2 annotations in Spring: #RequestBody and #ResponseBody. These annotations consumes, respectively produces JSONs. Some more info here.
I have a REST-Api where I want to validate the parameters on method Level. With #Valid and a custom object where validation annotations are inside, it works fine, but it fails, if I use a String and write the annotations directly into the method signiture.
My validator
#Documented
#Constraint(validatedBy = {})
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#Pattern(regexp = "^[A-Za-z]{5}[0-9]{3,128}$")
public #interface CustomValidator{
public abstract String message() default "Custom Message"
public abstract Class<?>[] groups() default {};
public abstract Class<?>[] payload() default {};
}
My controller class
#RestController
public class MyController {
...
}
What works but is not so nice Code Style
#Data
public class FoobarForm{
#NotNull
#CustomValidator
String foobar;
}
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkFoobar(#Valid #CustomValidator #PathVariable String foobar) {
What I want
#RequestMapping(value = "/foo/{bar}", method = RequestMethod.GET)
public Bank checkIban(#Validated #NotNull #CustomValidator #PathVariable String foobar) {
As you can see, I want to validate a String parameter on method Level with a custom Validator. But that does not work. If I put an invalid argument in the method, it passes and the validation is never performed.
Of course I can check the Regex in another way since it is a path variable, but I have the same scenario at other Methods and want to have a general Solution for this problem.
Info:
I currently use Spring Version: 4.0.7