Spring boot : Failed to save List<String> - spring-boot

Please I'm trying to save List in spring boot I failed to do it.
this is the entity :
#Column(name="services")
#ElementCollection
#NotBlank
private List<String> services = new ArrayList<String>();
This is Postman :
Postman result :
issue :
: No validator could be found for constraint 'javax.validation.constraints.NotBlank' validating type 'java.util.List<java.lang.String>'
I edit the function to save like this :
List<String> lstServices= new ArrayList<>();
carePost.getServices().forEach(item -> {
lstServices.add(item);
});
carePost.setServices(lstServices);
but still same error
Controller method :
#PostMapping("/addcarepost")
public ResponseEntity<?> createCarePost(#RequestBody CarePost carePost){
carePost.setCareserviceId(carePost.getCareserviceId());
List<String> lstServices= new ArrayList<>();
carePost.getService().forEach(item -> {
lstServices.add(item);
});
carePost.setService(lstServices);
carePostService.save(carePost);
return ResponseEntity.ok(new MessageResponse("CarePost registered successfully!"));
}
ServiceImpl :
#Override
public CarePost save(CarePost cp) {
return carePostRepository.save(cp) ; }

You should not use #NotBlank because:
#NotBlank can be applied only to text values and validates that the property is not null or whitespace.
instead you should use #NotEmpty because :
#NotEmpty validates that the property is not null or empty; can be applied to String, Collection, Map or Array values.
If your goal is to verify if in the array are some empty values, you can create a custom validator like shown here or you can use List<#NotBlank String> preferences;
Putting #NotBlank (or others validation annotation) at property level, will validate the property itself, if you wanna instead validate the element of a the property (in this case a collection)you have to put the annotation at the same element level (as shown on the top).
Source: Javax Validation

Related

javax-validation annotation is not working for member variables that is declared as another ObjectType

I have added annotations in the parent class.
It is working fine.
But it is not working in the member variables that is declared as another Object type. It is validating:
orderId from base class
referenceNumber from MarchantApplicationRequest
#NotEmpty annotation at customerRequests field in MerchantApplicationRequest.
But it is not validating customerRoleType in CustomerRequest.
Also, I would like to add #NotBlank annotation in customerRequests. But it is not taking this, though it is taking #NotEmpty annotation.
Class MerchantApplicationRequest
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
Class CustomerRequest
public class CustomerRequest {
#NotBlank(message="customerRoleType is mandatory")
private String customerRoleType ;
}
Controller class
Method where to apply validation:
#PostMapping("/orderDetail")
public void orderDetail(#Valid #RequestBody MerchantApplicationRequest request) {
try {
iOrderService.updateProductDetail(request);
} catch (Exception e) {
// ...
}
}
Here is my JSON payload:
{
"orderId" : 101,
"referenceNumber" : "123",
"customerRequests" : [ {
"customerRoleType" : null
}]
}
I am using in pom.xml of Spring Boot application:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
If you want to cascade the validation you have to add the #Valid annotation:
#Valid
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
Please read more about cascading in the Hibernate Validation documentation: Example 2.11: Cascaded validation
Using bean-validation (javax.validation), you can add validation to elements of collections.
Using Bean-Validation 1.0
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
#Valid
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
See also:
JSR 303: How to Validate a Collection of annotated objects?
LogicBig Tutorial: Collection Validation
Alternative since Bean-Validation 2.0
In Java 8 generic types can also be validated by prepending the annotation before the type inside the diamond-operator, e.g. <#Valid CustomerRequest>. This is a more concise way of defining per-element validation. It has the same effect like the traditional way, validates every given element as defined in the class (CustomerRequest).
See also:
java/beans validation - collection/map does not contain nulls
Baeldung Tutorial: Validating Container Elements with Bean Validation 2.0

Spring data mongo - unique random generated field

I'm using spring data mongo. I have a collection within a document that when I add an item to it I would like to assign a new automatically generated unique identifier to it e.g. (someGeneratedId)
#Document(collection = "questionnaire")
public class Questionnaire {
#Id
private String id;
#Field("answers")
private List<Answer> answers;
}
public class Answer {
private String someGeneratedId;
private String text;
}
I am aware I could use UUID.randomUUID() (wrapped in some kind of service) and set the value, I was just wondering if there was anything out of the box that can handle this? From here #Id seems to be specific to _id field in mongo:
The #Id annotation tells the mapper which property you want to use for
the MongoDB _id property
TIA
No there is no out of the box solution for generating ids for properties on embedded documents.
If you want to keep this away from your business-logic you could implement a BeforeConvertCallback which generates the id's for your embedded objects.
#Component
class BeforeConvertQuestionnaireCallback implements BeforeConvertCallback<Questionnaire> {
#Override
public Questionnaire onBeforeConvert(#NonNull Questionnaire entity, #NonNull String collection) {
for (var answer : entity.getAnswers()) {
if (answer.getId() == null) {
answer.setId(new ObjectId().toString());
}
}
return entity;
}
}
You could also implement this in a more generic manner:
Create a new annotation: #AutogeneratedId.
Then listen to all BeforeConvertCallback's of all entities and iterate through the properties with reflection. Each property annotated with the new annotation gets a unique id if null.

Throw error when properties marked with #JsonIgnore are passed

I have a requirement to mark certain properties in my REST beans as ignored using #JsonIgnore. (I am using Spring Boot). This helps in avoiding these properties in my Swagger REST documentation.
I also would like to ensure that if the client passes these properties, an error is sent back. I tried setting spring.jackson.deserialization.fail-on-unknown-properties=true, but that works only for properties that are truly unknown. The properties marked with #JsonIgnore passes through this check.
Is there any way to achieve this?
I think I found a solution -
If I add #JsonProperty(access = Access.READ_ONLY) to the field that is marked as #JsonIgnore, I get back a validation error. (I have also marked the property with #Null annotation. Here is the complete solution:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
#Null(message = "Id must not be passed in request")
private String id;
private String name;
//getters and setters
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class EmployeeRequest extends Employee {
#Override
#JsonIgnore
#JsonProperty(access = Access.READ_ONLY)
public void setId(String id) {
super.setId(id);
}
}
PS: By adding #JsonProperty(access = Access.READ_ONLY), the property started showing up in Swagger model I had to add #ApiModelProperty(hidden = true) to hide it again.
The create method takes EmployeeRequest as input (deserialization), and the get method returns Employee as response (serialization). If I pass id in create request, with the above solution, it gives me back a ConstraintViolation.
PS PS: Bummer. None of these solutions worked end-to-end. I ended up creating separate request and response beans - with no hierarchical relationship between them.

Spring Data Elasticsearch Repository query define date input parameter format

I am using elasticsearch 6.5.3 and Spring Boot 2.1.6 and spring-data-elasticsearch 3.2.0.M1.
I have defined the Elasticsearch configuration as:
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client(), new CustomEntityMapper());
}
public static class CustomEntityMapper implements EntityMapper {
private final ObjectMapper objectMapper;
public CustomEntityMapper() {
//we use this so that Elasticsearch understands LocalDate and LocalDateTime objects
objectMapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
//MUST be registered BEFORE calling findAndRegisterModules
.registerModule(new JavaTimeModule())
.registerModule(new Jdk8Module());
//only autodetect fields and ignore getters and setters for nonexistent fields when serializing/deserializing
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
//load the other available modules as well
objectMapper.findAndRegisterModules();
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
I have a repository with a method defined as:
List<AccountDateRollSchedule> findAllByNextRollDateTimeLessThanEqual(final LocalDateTime dateTime);
And the POJO AccountDateRollSchedule defines that field as:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime nextRollDateTime;
I see my index properly has that field created as declared and expected:
"nextRollDateTime": {
"type": "date",
"format": "date_hour_minute"
}
Also querying the index returns the field formatted as expected:
"nextRollDateTime" : "2019-06-27T13:34"
My repository query would translate to:
{"query":
{"bool" :
{"must" :
{"range" :
{"nextRollDateTime" :
{"from" : null,
"to" : "?0",
"include_lower" : true,
"include_upper" : true
}
}
}
}
}
}
But passing any LocalDateTime input to the method does NOT respect the format defined for the field, the FULL format is always used instead. Invoking:
findAllByNextRollDateTimeLessThanEqual(LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MINUTES));
gives me the following exception (any #DateTimeFormat or #JsonFormat annotation on the method parameter in the repository is ignored):
Unrecognized chars at the end of [2019-07-22T09:07:00.000]: [:00.000]
If I instead change the repository method to accept a String and pass a String formatted exactly as expected as input to it, it works no problem.
Is it possible to somehow define the format used for the date parameter passed in input to the repository method or have Spring use the one configured on the field itself?
I would like not to wrap that method for a simple conversion like this (I did and it works), and I would also like to avoid using long type for the date field
Thanks and cheers
For reference, I also open issue on Spring JIRA
These problems are one reason why we move away from using and exposing the JacksonMapper in Spring Data Elasticsearch. From version 4.0 on all you need on your property is the one annotation:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
private LocalDateTime nextRollDateTime;
This will then be used in writing the index mappings, when entities are indexed and retrieved, and also when repository method and queries are processed.
But for the 3.2.x version you will have to use a workaround like the wrapping you mentioned.

Spring Boot Controller endpoint and ModelAttribute deep access

I would like to know how to access a deep collection class attribute in a GET request. My endpoint maps my query strings through #ModelAttribute annotation:
Given that:
public class MyEntity
{
Set<Item> items;
Integer status;
// getters setters
}
public class Item
{
String name;
// getters setters
}
And my GET request: localhost/entities/?status=0&items[0].name=Garry
Produces bellow behavior?
#RequestMapping(path = "/entities", method = RequestMethod.GET)
public List<MyEntity> findBy(#ModelAttribute MyEntity entity) {
// entity.getItems() is empty and an error is thrown: "Property referenced in indexed property path 'items[0]' is neither an array nor a List nor a Map."
}
Should my "items" be an array, List or Map? If so, thereĀ“s alternatives to keep using as Set?
Looks like there is some problem with the Set<Item>.
If you want to use Set for the items collection you have to initialize it and add some items:
e.g. like this:
public class MyEntity {
private Integer status;
private Set<Item> items;
public MyEntity() {
this.status = 0;
this.items = new HashSet<>();
this.items.add(new Item());
this.items.add(new Item());
}
//getters setters
}
but then you will be able to set only the values of this 2 items:
This will work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa
This will not work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa&items[2].name=aaa
it will say: Invalid property 'items[2]' of bean class MyEntity.
However if you switch to List:
public class MyEntity {
private Integer status;
private List<Item> items;
}
both urls map without the need to initialize anything and for various number of items.
note that I didn't use #ModelAttribute, just set the class as paramter
#GetMapping("map")//GetMapping is just a shortcut for RequestMapping
public MyEntity map(MyEntity myEntity) {
return myEntity;
}
Offtopic
Mapping a complex object in Get request sounds like a code smell to me.
Usually Get methods are used to get/read data and the url parameters are used to specify the values that should be used to filter the data that has to be read.
if you want to insert or update some data use POST, PATCH or PUT and put the complex object that you want to insert/update in the request body as JSON(you can map that in the Spring Controller with #RequestBody).

Resources