I have created custom annotation like this
#Retention(RUNTIME)
#Target(METHOD)
#RequestMapping(consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE }, method = RequestMethod.POST)
public #interface JsonPostMapping {
#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 {};
}
I have the following controller
import com.config.requestmappings.JsonPostMapping;
#RestController
public class TestController {
#JsonPostMapping(value = "/hello")
public String hello() {
return "Hi, how are you";
}
}
The hello api is also being called with GET request, i am expecting that it should only be called upon POST request.
Remove the alias for method in the annotation and use #PostMapping instead of #RequestMapping. Set the defaults in the body of the annotation itself.
#PostMapping
public #interface JsonPostMapping {
// ...
#AliasFor(annotation = PostMapping.class)
String[] consumes() default { MediaType.APPLICATION_JSON_VALUE };
#AliasFor(annotation = PostMapping.class)
String[] produces() default { MediaType.APPLICATION_JSON_VALUE };
}
Related
I am trying to wrap org.springframework.data.elasticsearch.annotations.Document into a custom annotation , MyDocument.
#MyDocument will inherit everything that the parent #Document has, and the spring data library should also process MyDocument annotation that way it would do it for parent #Document.
Is this possible at all?
I tried below code, but I can't set the required 'indexName' param for #Document.
#Document() // HOW TO PUT indexName of #Mydocument into this?
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface MyDocument {
#AliasFor(annotation = Document.class, attribute = "indexName")
String indexName();
#AliasFor(annotation = Document.class, attribute = "useServerConfiguration")
boolean useServerConfiguration() default false;
#AliasFor(annotation = Document.class, attribute = "shards")
short shards() default 1;
#AliasFor(annotation = Document.class, attribute = "replicas")
short replicas() default 1;
#AliasFor(annotation = Document.class, attribute = "refreshInterval")
String refreshInterval() default "1s";
#AliasFor(annotation = Document.class, attribute = "indexStoreType")
String indexStoreType() default "fs";
#AliasFor(annotation = Document.class, attribute = "createIndex")
boolean createIndex() default true;
#AliasFor(annotation = Document.class, attribute = "versionType")
VersionType versionType() default VersionType.EXTERNAL;
// My document specific props
String myCustomProp() default "myDefault";
// more properties....
}
REFERENCE for #Document Annotation by spring data elasticsearch
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
public #interface Document {
String indexName();
#Deprecated
String type() default "";
boolean useServerConfiguration() default false;
short shards() default 1;
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
boolean createIndex() default true;
VersionType versionType() default VersionType.EXTERNAL;
}
EDITED : I actually needed to pass all the #Document params via this #MyDocument
EDIT#2 : Added #Document annotation class for reference
You are just missing the value argument of the #AliasFor annotation:
#Document() // HOW TO PUT indexName of #Mydocument into this?
#Persistent
#Inherited
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
public #interface MyDocument {
#AliasFor(value = "indexName", annotation = Document.class)
String indexName();
boolean useServerConfiguration() default false;
short shards() default 1;
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
boolean createIndex() default true;
VersionType versionType() default VersionType.EXTERNAL;
// My document specific props
String myCustomProp() default "myDefault";
// more properties....
}
Note that this only works in spring-data-elasticsearch version 4.2.x (and above).
#Controller......
#PutMapping(value = "/{id}")
public ResponseEntity<JsonNode> editStudent( #PathVariable #Positive(message = "Student id must be Positive Value") Long id, #Valid #ValidRequestBody(DTOClass = StudentDTO.class) #Validated(value = Update.class) #RequestBody(required = true) StudentDTO studentDTO, BindingResult bindingResult ) {.....
----------------------------------------------------------------------------
#Documented
#Retention(RUNTIME)
#Target({ TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.MODULE, ElementType.PACKAGE, ElementType.TYPE_PARAMETER, ElementType.TYPE_USE })
#Constraint(validatedBy = RequestBodyValidator.class)
public #interface ValidRequestBody {
String message()
default "Required parameter is missing";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<?> DTOClass();
}
-----------------------------------------------------
public class RequestBodyValidator implements ConstraintValidator<ValidRequestBody, Map<String, Object>> {
Class<?> dtoClass = null;
#Override
public void initialize( ValidRequestBody constraintAnnotation ) {
this.dtoClass = constraintAnnotation.DTOClass();
}
#Override
public boolean isValid( Map<String, Object> objectNode, ConstraintValidatorContext context ) {
Collection<Object> objectValues = objectNode.values();
return !objectValues.stream().allMatch( null );
}
}
You could use #NotNull over all fields
https://www.baeldung.com/java-bean-validation-not-null-empty-blank
You can use #Valid or #NonNull annotation on all fields in your DTO. So, if a field is received as null or empty, exception would be thrown.
I have a list of POST requests, where request bodies are quite similar
{
"entity":{
"type":"Nissan"
"parts":{
"Nissan_unique_content1":"value",
"Nissan_unique_content2":"value"
}
}
"updateDate":"Date..."
}
{
"entity":{
"type":"Ford"
"parts":{
"Ford_unique_content1":"value",
"Ford_unique_content2":"value",
"Ford_unique_content3":"value"
}
}
"updateDate":"Date..."
}
I have a generic RequestBody
public class RequestBody<T>{
EntityBody<T> entity;
Date updateDate;
}
public class EntityBody<T>{
String type;
T parts;
}
In my Post Controller I have method as
#RequestMapping(value = "/{type}")
public ResponseEntity<?> create(
#PathVariable(value = "type") String type,
#RequestBody RequestBody<T> body) {
...
}
Is there anyway that generic type T can be assigned depends on type?
In this case I wouldn't need create multiple create method, otherwise I need create multiple method, like
#RequestMapping(value = "/nissan")
public ResponseEntity<?> createNissan(
#RequestBody RequestBody<NissanContent> body) {
...
}
#RequestMapping(value = "/ford")
public ResponseEntity<?> createFord(
#RequestBody RequestBody<Ford> body) {
...
}
which are unnecessary repetitions.
This can be done by using #JsonTypeInfo annotation.
For example:
Define entities according to different structures under "parts" key:
class NissanParams {
#JsonProperty("Nissan_unique_content1")
private String nissanUniqueContent1;
#JsonProperty("Nissan_unique_content2")
private String nissanUniqueContent2;
// getters + setters
}
In EntityBody, remove type field and add the annotations:
public class EntityBody<T> {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({ #JsonSubTypes.Type(value = NissanParams.class, name = "Nissan")})
private T parts;
}
And there will be a single controller method:
#PostMapping(path = "{make}",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public RequestBody<Object> create(#PathVariable("make") String make,
#org.springframework.web.bind.annotation.RequestBody RequestBody<Object> body) {
// please change the name of "RequestBody" entity, in order to avoid name clash with the annotation
}
You can use JsonTypeInfo and JsonSubTypes Jackson annotations. Your model could look like:
class EntityBody {
private Car parts;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = JsonTypeInfo.As.EXTERNAL_PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(name = "Ford", value = Ford.class),
#JsonSubTypes.Type(name = "Nissan", value = Nissan.class)
})
public Car getParts() {
return parts;
}
}
As you can see, you do not need type property. It will be read by Jackson to find out a car type. I have created Car base class/interface but you do not need to do that.
Your POST method could look like this:
#RequestMapping(value = "/cars", method = RequestMethod.POST)
public ResponseEntity<?> create(#RequestBody RequestPayload body) {
logger.info(body.toString());
return ResponseEntity.ok("OK");
}
You do not need PathVariable here.
I have problem in my controller with optional params in requestmapping, look on my controller below:
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Books>> getBooks() {
return ResponseEntity.ok().body(booksService.getBooks());
}
#GetMapping(
produces = MediaType.APPLICATION_JSON_VALUE,
params = {"from", "to"}
)
public ResponseEntity<List<Books>>getBooksByFromToDate(
#RequestParam(value = "from", required = false) String fromDate,
#RequestParam(value = "to", required = false) String toDate)
{
return ResponseEntity.ok().body(bookService.getBooksByFromToDate(fromDate, toDate));
}
Now, when I send request like:
/getBooks?from=123&to=123
it's ok, request goes to "getBooksByFromToDate" method
but when I use send something like:
/getBooks?from=123
or
/getBooks?to=123
it goes to "getAlerts" method
Is it possible to make optional params = {"from", "to"} in #RequestMapping ? Any hints?
Use the default values. Example:-
#GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Books>> getBooksByFromToDate(#RequestParam(value = "from", required = false, defaultValue="01/03/2018") String fromDate, #RequestParam(value = "to", required = false, defaultValue="21/03/2018") String toDate) {
....
}
Just use defaultValue as explained in the Spring's docs:
defaultValue
The default value to use as a fallback when the request parameter is
not provided or has an empty value.
javax validation not working on method parameters.. This is a test code and none of javax validation works on method parameter...
#RequestMapping(value = "/{id}", method = RequestMethod.PUT, params = "action=testAction")
public Test update(
#Size(min = 1) #RequestBody List<String> ids,
#Min(3) #PathVariable String name) {
return doSomething(ids, name);
}
But i have class level validations which works perfectly...
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public RoleType create (#RequestBody #Validated(FieldType.class) User user) {
...
}
And
#Size(min = 2, max = 10, groups = { FieldType.class }, message = "Invalid user code")
public String getId() {
return _id ;
}
-- Solution --
all steps followed as per the accepted answer.
And another addition is annoation on class level
#Validated
class UserController
{
#RequestMapping(value = "/{id}", method = RequestMethod.PUT, params ="action=testAction")
public Test update(#Size(min = 1) #RequestBody List<String> ids,#Min(3) #PathVariable String name) {
return doSomething(ids, name);
}
}
you need to register MethodValidationPostProcessor bean to kick method level validation annotation
delegates to a JSR-303 provider for performing method-level
validation on annotated methods.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
then,
#RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public Test update(
#Size(min = 1) #RequestBody List<String> ids,
#Min(3) #PathVariable("id") String name) {
return doSomething(ids, name);
}
if you want to handle validation exception
#ExceptionHandler(value = { ConstraintViolationException.class })
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public String handleResourceNotFoundException(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> violation : violations ) {
strBuilder.append(violation.getMessage() + "\n");
}
return strBuilder.toString();
}