How to customize the validation annotation based on some criteria using javax validation with Hibernate validation implementation.
Sample code:
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import org.springframework.stereotype.Component;
import com.kp.mechanic.workshop.validator.beans.GeoLocation;
import com.kp.mechanic.workshop.validator.beans.Practioner;
import com.kp.mechanic.workshop.validator.beans.PractionerAddress;
#Component("validator")
public class ValidatorService {
private Validator validator;
private ValidatorFactory factory;
public void execute() {
System.out.println(" Validation framework starts");
try {
// < DAO call to get GEO Location is AUS >
//Construct Geo Location:
GeoLocation geoLocation= new GeoLocation();
geoLocation.setStrtAddrLine1("walker street ");
geoLocation.setOptionalAddrLine2("bonitoa road");
geoLocation.setZipCD("SY");
factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set<ConstraintViolation<Object>> resultSet= validator.validate(geoLocation);
for (ConstraintViolation<Object> object : resultSet) {
System.out.println(object.getPropertyPath() + ": " + object.getMessage());
}
}
catch(Exception e) {
System.out.println("Message "+e);
e.printStackTrace();
}
}
}
Refer the given below POJO using Lombok.
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
public class GeoLocation {
//City name
#NotNull(message="warn:Missing data")
#Size(min =3 , max =50, message = "warn:Invalid length")
#Pattern(regexp = "^a-zA-Z",message = "warn:Invalid characters found in text",flags=Pattern.Flag.CASE_INSENSITIVE)
private String cityNM;
//State Code
#NotNull(message="warn:Missing data")
#Size(min =2 , max =2, message = "warn:Invalid Length")
#Pattern(regexp = "^[a-zA-Z]",message = "warn:Invalid characters found in text",flags=Pattern.Flag.CASE_INSENSITIVE)
private String stateCD;
//zip code
#NotNull(message="warn:Missing data")
#Size(min =5 , max =9, message = "warn:Invalid Length")
#Pattern(regexp = "^[0-9]",message = "warn:Invalid characters found in text")
private String zipCD;
}
Using given above pom entries .
The above code is working fine for the given below validation rules for GEO Location is "AUS".
> CityName : not null , minimum 3 and maximum 50 characters, only alphabets.
> State Code : not null , maximum 2 , only alphabets.
> Zip Code : not null , minimum 5 and maximum 9, only digits.
Where as for "IND" , i would like to change the given below validation rules as such.
> CityName : not null , minimum 10 and maximum 15 characters, only alphabets
> State Code : not null , maximum 6, only alphabets.
> Zip Code : not null , maximum 10, only digits
Can you give any suggestion to change the validation rules based on the geo location type is IND?
This is a kind of Custom Annotation, is there any better approach to reuse the annotation without writing java logic in custom annotation class ?
Custom Annotation for cross fields
First of all appreciate for a nice question.
In above scenario I would go for custom annotation based validator as shown below.
package com.example.demo;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Constraint(validatedBy = StateCodeValidator.class)
#Documented
#Target({METHOD, FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface StateValidate {
String message() default "Invalid State";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.example.demo;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
public class StateValidator implements ConstraintValidator<StateValidate, String>{
#Autowired
private HttpServletRequest httpServletRequest;
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
//TODO you can check code size as per requirement
}
}
}
You need to annotate #StateValidate at any field you looking for
This will be more controlled approach and if needed i18 can be used to
manage to support multiple countries.
I got reference from given below links.
stackover flow .
Programmatic constraint definition
HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
ConstraintMapping constraintMapping = configuration.createConstraintMapping();
if(geoLocation.getType().equals("AUS")) {
constraintMapping
.type(GeoLocation.class)
.property("cityName", ElementType.FIELD)
.constraint(new NotNullDef().message("cityName should not empty"))
.constraint(new SizeDef().min(3).message("Minimum 3 charater"))
.constraint(new SizeDef().max(50).message("Maximum 50 character"));
}else if(geoLocation.getType().equals("IND")) {
constraintMapping
.type(GeoLocation.class)
.property("cityName", ElementType.FIELD)
.constraint(new NotNullDef().message("cityName should not empty"))
.constraint(new SizeDef().min(10).message("Minimum 10 charater"))
.constraint(new SizeDef().max(15).message("Maximum 15 character"));
}
validator=configuration.addMapping(constraintMapping).buildValidatorFactory().getValidator();
resultSet= validator.validate(geoLocation);
for (ConstraintViolation<Object> object : resultSet) {
System.out.println(object.getPropertyPath() + ": " + object.getMessage());
}
Related
I am trying to add the BusinessInformation data in my existing JSON.
{
"Original": "Web Form",
"SubmitterNetworkName": "null",
"SourceName": "Contact Request Form",
"SourceKind": "Web Form",
** "BusinessInformation": {
"BusinessContactName": null,
“AccountNumber”:null,
},**
"EmployeeName": null,
"EmployeeDOB": null,
}
So I have a Spring Batch app that exports data from a database to another DB and during that run some JSON needs to be created mapping multiple columns. I am using lombok and jackson mainly for the creation of JSON.
The model sample that I have. I also tried creating BusinessInformation class.
package model
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#JsonIgnoreProperties(ignoreUnknown = true)
public class ColumnNameForTheDBImImportingTo implements Serializable {
#JsonProperty("Origin")
private String Origin;
and so on..
}
And then I have the Service for it.
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import twc.batch.extcbk.model.*;
import java.io.IOException;
#Component
#Slf4j
public class ColumnNameForTheDBImImportingToService {
private ColumnNameForTheDBImImportingTo columnNameForTheDBImImportingTo (some codes) {
ColumnNameForTheDBImImportingTo columnNameForTheDBImImportingTo = ColumnNameForTheDBImImportingTo .builder().build();
columnNameForTheDBImImportingTo.setOrigin("Web Form");
}
**Then I have a method for the object mapper.**
private String getColumnNameForTheDBImImportingTo (ColumnNameForTheDBImImportingTo columnNameForTheDBImImportingTo ) {
String columnNameForTheDBImImportingToStr = null;
try {
ObjectMapper mapper = new ObjectMapper();
columnNameForTheDBImImportingToStr = mapper.writeValueAsString(columnNameForTheDBImImportingTo );
log.debug("columnNameForTheDBImImportingToStr {}", columnNameForTheDBImImportingToStr );
} catch (IOException e) {
log.error("getColumnNameForTheDBImImportingTo " + e.getMessage());
}
return columnNameForTheDBImImportingToStr ;
}
**Then another class builds the db columns and sets the values.**
I've tried following the Baeldung but I don't quite understand it.
I tried creating a new class for the business information and i was thinking of inserting it in the ColumnNameForTheDBImImportingTo.
Please refer me to any useful information I can follow for this problem.
Thanks!
Data transformation is a typical use case for an item processor.
The way I would implement this with Spring Batch is configure a chunk-oriented step defined as follows:
A JsonItemReader to read the input file
An item processor that adds the business information
A JsonItemWriter to write the output file
I am very new to Java and Spring Boot and trying to implement validation on the variable of a class as shown below and wants to validate each of the variables while initializing the value using the setter of the class.
import javax.validation.constraints.Digits;
import javax.validation.constraints.Size;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
public class LoanRequestBean {
#Size(max = 8)
String uniqueRefBancs;
#Digits(integer=2)
Integer loanId;
#Size(max = 20)
String loanIban;
}
The variable initialization is done by the setter as shown below.
import org.springframework.stereotype.Component;
#Component
public class BancsRequestRecords {
private String[] fieldsArray;
#Valid
LoanRequestBean loanRequest = new LoanRequestBean();
public loanRequestBean RequestParser(String message) {
fieldsArray = message.split("¬");
loanRequest.setUniqueRefBancs(fieldsArray[0]);
loanRequest.setLoanId(Integer.valueOf( fieldsArray[1]));
loanRequest.setLoanIban(fieldsArray[2]);
return loanRequest;
}
}
When I try to invoke Requestparser method with message as 2028110600000001000752¬123456¬FI9080706050403020 in the argument of the method, the values are not getting validated. eg: uniqueRefBancs has max length of 8 but still it does not throw any error while initialization. Could you please help me if it's the right approach to validate the variables or any any other way to achieve it?
Bean Validation is performed when the #Valid or #Validated annotations are used on a method parameter. Further details here. You could use a method to validate your loanRequestBean. To check the result of validation you can use BindingResult. Edit: Unrelated but I would consider renmaing loanRequestBean to LoanRequestBean to follow java class name conventions.
You would need to programmatically trigger the validation of your loanRequest object as follows:
import org.springframework.stereotype.Component;
#Component
public class BancsRequestRecords {
private LoanRequestBean loanRequest = new LoanRequestBean();
private Validator validator;
public BancsRequestRecords(Validator validator) {
this.validator = validator;
}
public loanRequestBean RequestParser(String message) {
String[] fieldsArray = message.split("¬");
loanRequest.setUniqueRefBancs(fieldsArray[0]);
loanRequest.setLoanId(Integer.valueOf( fieldsArray[1]));
loanRequest.setLoanIban(fieldsArray[2]);
Set<ConstraintViolation<LoanRequestBean>> violations = validator.validate(loanRequest);
return loanRequest;
}
}
You can read more about this at https://www.baeldung.com/javax-validation#programmatic.
I am trying to create an object model that represents a hierarchy of nested device locations. For example a 'deck' contains a 'slide tray' which contains one or more 'slides'. I want to be able to read in a json file that contains the hierarchy/configuration of the system. I want to use Lombok builders in my classes so I can safely generate the json files in code when I need to. The more common use case is to read in the json file to create the pojo's on application startup. Generating the json files with the builder works great. However, I have not been to de-serialize the file back into pojo's.
Here is the error I am getting:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `my.org.Deck$DeckBuilder` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"type":"Deck","locNumber":1,
The top level super-class is this:
package my.org;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Getter;
import lombok.Singular;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import java.awt.geom.Point2D;
import java.util.List;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Deck.class, name = "Deck"),
#JsonSubTypes.Type(value = SlideTray.class, name = "SlideTray"),
#JsonSubTypes.Type(value = Slide.class, name = "Slide"),
#JsonSubTypes.Type(value = NullLoc.class, name = "null"),
})
#SuperBuilder
#Getter
#Accessors(fluent = true, chain = true)
#JsonDeserialize(builder = BaseLocationType.BaseLocationTypeBuilder.class)
public class BaseLocationType<T extends BaseLocationType> {
#JsonProperty("locNumber")
private int locNumber;
#JsonProperty("posRelativeToParent")
private Point2D.Double positionRelativeToParent;
#Singular
#JsonProperty("childLocs")
private List<T> childLocs;
}
The Deck sub-class:
package my.org;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
#SuperBuilder
#Getter
#EqualsAndHashCode(callSuper = true)
#Accessors(fluent = true, chain = true)
#JsonDeserialize(builder = Deck.DeckBuilder.class)
public class Deck extends BaseLocationType<SlideTray> {
private String deckField1;
private String deckField2;
}
The SlideTray sub-class:
package my.org;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
#SuperBuilder
#Getter
#EqualsAndHashCode(callSuper = true)
#Accessors(fluent = true, chain = true)
#JsonDeserialize(builder = SlideTray.SlideTrayBuilder.class)
public class SlideTray extends BaseLocationType<Slide> {
private String slideTrayField1;
}
The Slide sub-class:
package my.org;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
#SuperBuilder
#Getter
#EqualsAndHashCode(callSuper = true)
#Accessors(fluent = true, chain = true)
#JsonDeserialize(builder = Slide.SlideBuilder.class)
public class Slide extends BaseLocationType<NullLoc> {
private String slideField1;
}
NullLoc:
package my.org;
import lombok.experimental.SuperBuilder;
#SuperBuilder
public class NullLoc extends BaseLocationType<NullLoc> {
// no fields or builder, etc
}
Test Code - fails with the above exception on mapper.readValue():
// create 1 deck with 1 slideTray that has 2 slides
Deck.DeckBuilder<?, ?> deckBuilder = Deck.builder()
.locNumber(1)
.positionRelativeToParent(new Point2D.Double(1.0, 1.0))
.deckField1("deck f1 data")
.deckField2("deck f2 data")
.childLoc(SlideTray.builder()
.locNumber(2)
.positionRelativeToParent(new Point2D.Double(2.0, 2.0))
.slideTrayField1("slide tray f1 data")
.childLoc(Slide.builder()
.locNumber(3)
.positionRelativeToParent(new Point2D.Double(3.0, 3.0))
.slideField1("child1-slide f1 data")
.build())
.childLoc(Slide.builder()
.locNumber(4)
.positionRelativeToParent(new Point2D.Double(4.0, 4.0))
.slideField1("child2-slide f1 data")
.build()).build());
Deck deckPojo = deckBuilder.build();
// serialize the pojo's
String json = new ObjectMapper().writeValueAsString(deckPojo);
// de-serialize the json back into the pojo's
ObjectMapper mapper = new ObjectMapper();
Deck deckPojoDeserialized = mapper.readValue(json, Deck.class);
The json that is generated:
{
"type": "Deck",
"locNumber": 1,
"posRelativeToParent": {
"x": 1.0,
"y": 1.0
},
"childLocs": [
{
"type": "SlideTray",
"locNumber": 2,
"posRelativeToParent": {
"x": 2.0,
"y": 2.0
},
"childLocs": [
{
"type": "Slide",
"locNumber": 3,
"posRelativeToParent": {
"x": 3.0,
"y": 3.0
},
"childLocs": []
},
{
"type": "Slide",
"locNumber": 4,
"posRelativeToParent": {
"x": 4.0,
"y": 4.0
},
"childLocs": []
}
]
}
]
}
note: I'm not seeing a option here in stackoverflow to upload the demo-project zip file... but can figure out a way to share that if needed.
Thanks!
I think the root problem is related to the #JsonDeserialize annotation builder values defined across the three primary sub-classes, because they appear to be abstract class references. Which would also explain the error message you're receiving.
From the Lombok #SuperBuilder documentation ref:
To ensure type-safety, #SuperBuilder generates two inner builder classes for each annotated class, one abstract and one concrete class named FoobarBuilder and FoobarBuilderImpl (where Foobar is the name of the annotated class).
I believe updating the following #JsonDeserialize annotation builder values will help resolve the issue:
In the Deck sub-class:
#JsonDeserialize(builder = Deck.DeckBuilderImpl.class)
In the SlideTray sub-class:
#JsonDeserialize(builder = SlideTray.SlideTrayBuilderImpl.class)
In the Slide sub-class:
#JsonDeserialize(builder = Slide.SlideBuilderImpl.class)
Additional note with respect to BuilderImpl manual updates:
The #SuperBuilder documentationref includes the following supporting information relative to this topic:
Customizing the code generated by #SuperBuilder is limited to adding new methods or annotations to the builder classes, and providing custom implementations of the 'set', builder(), and build() methods. You have to make sure that the builder class declaration headers match those that would have been generated by lombok. Due to the heavy generics usage, we strongly advice to copy the builder class definition header from the uncustomized delomboked code.
Spring-MVC's #RequestMapping annotation has parameter "name" which can be used for identifying each resource.
For some circumstances I need access this information on fly: retrieve mapping details (e.g. path) by given name.
Sure I can scan for classes for this annotation and retrieve needed instances via:
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RequestMapping.class));
// ... find classes ... go through its methods ...
But it is quite ugly. Is any more simple solution?
You can use RequestMappingHandlerMapping to get all your mappings and filter them based on name. Following is a code snippet which creates a rest api and returns the path details of a api/mapping.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
#RestController
public class EndpointController {
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#GetMapping("endpoints/{name}")
public String show(#PathVariable("name") String name) {
String output = name + "Not Found";
Map<RequestMappingInfo, HandlerMethod> methods = this.handlerMapping.getHandlerMethods();
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : methods.entrySet()) {
if (entry.getKey().getName() != null && entry.getKey().getName().equals(name)) {
output = entry.getKey().getName() + " : " + entry.getKey();
break;
}
}
return output;
}
}
The above is just an example, You can use the RequestMappingHandlerMapping anyway you want until you can autowire it.
I have the typical example, where a POST has many TAGS, and a TAG has many POSTs.
Instead of using a typical #ManyToMany, I use a domain object in the middle, called TAGPOST, which also allows me to have useful data in there, such as when a post was tagged with a given tag, etc. Each POST, and TAG resp, is in a #OneToMany relationship with a TAGPOST.
The specific requirement is that a post cannot have the same tag included twice, therefore the TAGPOST.post and TAGPOST.tag pair must always be unique. Normally, I would do that by making a composite primary key pair in the table, responsible for storing TAGPOST objects.
AFAIK, there is no way to express this unique constraint. I have marked jpa.ddl=update, which means that every time I move the application to a new environment, I will have to go and manually fix this in the DB. This is very inconvenient, and error prone, especially when unit testing, because then the database is created and dropped more or less in every iteration.
I was even thinking to do the check manually on #PrePersist, or even move the check in a business layer, say, create a PostService.
What do I do? Am I missing something that Play has by default? Some clever annotation to express the uniqueness of the #ManyToOne properties of the TAGPOST class?
FYI: I am using Play 1.2.5
EDIT: The TAGPOST class looks like this:
#Entity
public class TagPost extends Model {
#ManyToOne
public Tag tag;
#ManyToOne
public Post post;
public Date dateAdded;
...
}
I wrote a custom Check for db uniqueness. Maybe you should customize it.
DBUnique.java
package models.check;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.sf.oval.configuration.annotation.Constraint;
import play.db.jpa.GenericModel;
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.FIELD, ElementType.PARAMETER })
#Constraint(checkWith = DbUniqueCheck.class)
public #interface DBUnique {
String message() default DbUniqueCheck.mes;
Class<? extends GenericModel> modelClass();
String field() default ""; // field name will be used
}
DbUniqueCheck.java
package models.check;
import net.sf.oval.Validator;
import net.sf.oval.configuration.annotation.AbstractAnnotationCheck;
import net.sf.oval.context.FieldContext;
import net.sf.oval.context.OValContext;
import org.apache.commons.lang.StringUtils;
import play.db.jpa.GenericModel.JPAQuery;
#SuppressWarnings("serial")
public class DbUniqueCheck extends AbstractAnnotationCheck<DBUnique> {
final static String mes = "validation.dbunique";
DBUnique dbUnique;
#Override
public void configure(DBUnique dBUnique) {
this.dbUnique = dBUnique;
setMessage(dBUnique.message());
}
public boolean isSatisfied(Object validatedObject, Object value, OValContext context, Validator validator) {
try {
String field = dbUnique.field();
if (field == null || field.isEmpty()) {
field = ((FieldContext) context).getField().getName();
}
JPAQuery q = (JPAQuery) dbUnique.modelClass().getMethod("find", String.class, Object[].class)
.invoke(null, "by" + StringUtils.capitalize(field), new Object[] { value });
return q.first() == null;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
usage : link to gist
it simply checks the given field for given class instance is unique in db. Maybe you should make something like these..