Validate object field by conditions using Spring 3 - java

I have a complex object that contains two BigDecimal field
public class Test
{
private BigDecimal property1;
private BigDecimal property2;
//setter and getter method
}
Now when user entered property1 in spring form It should be 10% of property2.
This validation i have to do using spring validation framework.

public class TestValidator implements Validator {
public boolean supports(Class clazz) {
return Test.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
Test t = (Test) obj;
if(t.getProperty1()!=0.1*t.getProperty2()){
e.rejectValue("Test", "property1 not 10% of property2");
}
}
}

Related

Micronaut fails to load and use my `TypeConverter` implementation

I have a controller method, that takes in a POJO.
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Dto {
private LocalDate endDate;
private String token;
private TransactionType type;
}
Transaction type is a simple enum, but I want to use a custom conversion from the inbound value to the transaction type.
#Slf4j
#Controller("/api/transactions")
public class IssuerTransactionController {
#Get(value = "/{?tr*}", produces = APPLICATION_JSON)
public List<String> get(Dto tr) {
return new ArrayList<>();
}
}
I have written a converter:
#Slf4j
#Singleton
public class TransactionTypeConverter implements TypeConverter<String, TransactionType> {
#Override
public Optional<TransactionType> convert(String value, Class<TransactionType> targetType, ConversionContext context) {
return Arrays.stream(TransactionType.values())
.filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
.findFirst();
}
}
Micronaut is not using the type converter to transform the inbound value?
Is some special registration process needed in order for Micronaut to know that it should be using the converter?
If I add a constructor to TransactionTypeConverter I can see that the class is never actually created by Micronaut at all.
If I add it as a regular dependency to the controller, it's loaded (no surprise there), but still not used. Is there a step I am missing?
Seems you are using the Binding from Multiple Query values functionality which under the hood is just creating the map of the query parameters you passed in and uses the Jackson to convert the map into your own POJO. So it does not rely on the system converters but only on the Jackson itself.
What you can do is just use Jacksons #JsonCreator annotation to customize the conversation.
Something like this should work.
public enum TransactionType {
A ("A"),
B ("B");
private final String transactionType;
TransactionType(String transactionType){
this.transactionType = transactionType;
}
public String getTransactionType() {
return transactionType;
}
#JsonCreator
public static TransactionType forValue(Collection<String> values) {
if(values == null || values.isEmpty()){
return null;
}
String value = values.get(0);
return Arrays.stream(TransactionType.values())
.filter(txnType -> StringUtils.equals(txnType.getTransactionType(), value) || StringUtils.equals(txnType.name(), value))
.findFirst().orElse(null);
}
}

Validate at least one of three field in dto spring boot

I have a DTO that looks something like this:
class VehicleDto {
private String type;
private Car car;
private Bike bike;
}
Now depending on the type, I need to validate on at least one of Car and Bike.
Both cannot be present in the same request.
How can I do that?
Having two fields in class, while only one of them can present, seems like a design smell for me.
But if you insist on such design - you can create a custom Validator for your VehicleDto class.
public class VehicleValidator implements Validator {
public boolean supports(Class clazz) {
return VehicleDto.class.equals(clazz);
}
public void validate(Object obj, Errors errors) {
VehicleDto dto = (VehicleDto) obj;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "type",
"error.message.for.type.field");
if (null != dto.getType()
&& null != dto.getCar()
&& null != dto.getBike()) {
switch(dto.getType()) {
case "car":
errors.rejectValue("bike", "error.message.for.bike.field");
break;
case "bike":
errors.rejectValue("car", "error.message.for.car.field");
break;
}
}
}
}
Also, see Spring documentation about validation:
Validation using Spring’s Validator interface
Resolving codes to error messages
Injecting a Validator
For example, if we want to check whether my TaskDTO object is valid, by comparing its two attributes dueDate and repeatUntil , following are the steps to achieve it.
dependency in pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
DTO class:
#ValidTaskDTO
public class TaskDTO {
#FutureOrPresent
private ZonedDateTime dueDate;
#NotBlank(message = "Title cannot be null or blank")
private String title;
private String description;
#NotNull
private RecurrenceType recurrenceType;
#Future
private ZonedDateTime repeatUntil;
}
Custom Annotation:
#Constraint(validatedBy = {TaskDTOValidator.class})
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidTaskDTO {
String message() default "Due date should not be greater than or equal to Repeat Until Date.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Constraint Validator:
public class TaskDTOValidator implements ConstraintValidator<ValidTaskDTO, TaskDTO> {
#Override
public void initialize(ValidTaskDTO constraintAnnotation) {
}
#Override
public boolean isValid(TaskDTO taskDTO, ConstraintValidatorContext constraintValidatorContext) {
if (taskDTO.getRecurrenceType() == RecurrenceType.NONE) {
return true;
}
return taskDTO.getRepeatUntil() != null && taskDTO.getDueDate().isBefore(taskDTO.getRepeatUntil());
}
}
Make sure that you have #Valid in front of RequestBody of a postmapping method in your RestController. Only then the validation will get invoked:
#PostMapping
public TaskReadDTO createTask(#Valid #RequestBody TaskDTO taskDTO) {
.....
}
I hope this helps. If you need a detailed explanation on steps, have a look at this video

Spring MVC 3 JSON serializer returning backing fields and ignoring encapsulation

I have a controller returning JSON:
public #ResponseBody ResourcesModel data(#PathVariable(value = "id") long id, #PathVariable(value = "page") int page){
//populate model
}
Here is the relevant portion of the model:
public class ResourcesModel {
private boolean showLeft;
public boolean getShowLeft(){
return getPage() > 1;
}
public void setShowLeft(boolean b) { /*doesn't do anything*/ }
}
Without the backing field, showLeft does not get serialized. During serialization, it just looks at the field and ignores the getter. I find this very annoying... what is the point of encapsulation if it is ignored?
I know there are work arounds like setting showLeft in the controller, but I'd like to avoid this and use proper OO. Any suggestions to give the serializer hints?
I think that Just you have to add #JsonSetter to the field showLeft annotation
public class ResourcesModel {
private boolean showLeft;
public boolean getShowLeft(){
return getPage() > 1;
}
#JsonSetter
public void setShowLeft(boolean b) { /*doesn't do anything*/ }
}

Validate annotation on getter in Spring 4

How to make delegate fields validation (using annotation) in wrapper? Is it possible to make annotation on getter instead on fields?
I tried to do something like this:
class A{
private Object obj;
public Object getObj(){
return obj;
}
public void setObj(Object obj){
this.obj = obj;
}
}
#Component
class Wrapper{
A a;
public Wrapper(A a){
this.a = a;
}
#NotNull
public getObj(){
a.getObj();
}
public setObj(Object obj){
a.setObj(obj);
}
}

Spring MVC validation: minimum one field required. At least one field required

I do a search by criteria with an DTO entity for filter in the front-end of my application:
public class MyFilter implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
#Enumerated(EnumType.STRING)
private AccessType accessType;
private List<MyType> userType;
private List<OfficeLocation> officeLocation;
private List<Language> languages;
private String country;
}
and getters and setters.
In my controller:
#RequestMapping
public ModelAndView list(#ModelAttribute("filter") MyFilter myFilter, BindingResult result) {
final ModelAndView mav = new ModelAndView("list");
// validate
MyFilterValidator.validate(myFilter, result);
mav.addObject("filter", myFilter);
if (result.hasErrors()) {
return mav;
}
// ...
return mav;
}
I want to validate the search form filter by a validator class:
public class MyFilterValidator implements org.springframework.validation.Validator {
#Override
public void validate(Object object, Errors errors) {
final MyFilter myFilter = (MyFilter) object;
if (myFilter == null) {
errors.reject("error.one.field.required");
} else {
if (StringUtils.isEmpty(myFilter.getName()) && myFilter.getAccessType() == null
&& myFilter.getUserType() == null && myFilter.getLanguages() == null
&& StringUtils.isEmpty(myFilter.getCountry())
&& myFilter.getOfficeLocation() == null) {
errors.reject("error.one.field.required");
}
}
}
#Override
public boolean supports(Class inClass) {
return MyFilter.class.equals(inClass);
}
}
I need to validate if one field is filled, minimum one field of my Filter class is filled. How can I do that in a simple way?
I need to check each attribute : StringUtils.isEmpty or .size()<=0, ... ?
Is it possible to iterate over each property and check if one of them is not null?
To know if one field is fill?
If you need this test very often, then it would be worth to implement a small function that inspect some annotated fields of the DAO by reflection.
public DAO {
public String nevermind;
#AtLeastOneOfThem
public String a;
#AtLeastOneOfThem
public String b;
}
/**
* Return True if at least on of the fields annotated by #AtLeastOneOfThem
* is not Empty.
* THIS IS PSEUDO CODE!
*/
public static boolean atLeastOneOfThemIsNotEmpty(Object o) {
for(Field field : getFieldsAnnotatedWith(AtLeastOneOfThem.class, o) {
if (field.get() != null && !field.get().empty()) {
return true;
}
}
return false;
}
If this is too much work, then it would be the fastet way to implment the check in the tradtional handwritten way.

Categories