Is there a better way of writing a Java validator which ensures that a start date is before an end date than writing a class level ConstraintValidator in the following manner:
// VALIDATOR IMPLEMENTATION
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, Object> {
// cannot use LocalDate here...
private String start;
private String end;
#Override
public void initialize(final StartBeforeEndDateValid annotation) {
start = annotation.start();
end = annotation.end();
}
#Override
public boolean isValid(final Object bean, final ConstraintValidatorContext context) {
try {
final String startDateStr = BeanUtils.getProperty(bean, start);
final String endDateStr = BeanUtils.getProperty(bean, end);
final LocalDate startDate = new LocalDate(startDateStr);
final LocalDate endDate = new LocalDate(endDateStr);
return !startDate.isAfter(endDate);
} catch (final Exception e) {
return false;
}
}
}
// USAGE
#StartBeforeEndDateValid(start = "startDate", end = "endDate")
#Entity
public class MyBean {
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate startDate;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate endDate;
...
}
I don't really like the fact that I have to use reflection to extract the 2 date objects from the bean. Unfortunately afaik the validation spec does not specify a way to set only the values you want to validate from the bean.
One way would be to add an interface to MyBean
public interface StartEndDateable {
public LocalDate getStartDate();
public LocalDate getEndDate();
}
public class MyBean implements StartEndDatable {
...
Then you can set the generic type on ConstraintValidator to the new interface instead of Object.
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, StartEndDatable> {
#Override
public void initialize(StartBeforeEndDateValid annotation) {
}
#Override
public boolean isValid(StartEndDatable bean, ConstraintValidatorContext context) {
final LocalDate startDate = bean.getStartDate();
final LocalDate endDate = bean.getEndDate();
return !startDate.isAfter(endDate);
}
}
Obviously any class you then want to validate with the start and end date will have to implement the StartEndDateable (Not the best name, I know, but I'm sure you can think of something better) and define the getStartDate and getEndDate methods.
Related
I have a method that is filtering Visits (visit have date, and animalType, and medical specialisation) by given parameters.I am using queryDSL to filter list. This is my meth
private List<Visit> customQuery(CustomSearchCommand customSearchCommand){
QVisit visit = QVisit.visit;
JPAQuery<Visit> visits = new JPAQueryFactory(entityManager).selectFrom(visit)
.where(visit.veterinarian.medicalSpecialization.eq(customSearchCommand.getMedicalSpecialization()))
.where(visit.date.after(customSearchCommand.getDateFrom()))
.where(visit.date.before(customSearchCommand.getDateTo()))
.where(visit.veterinarian.animalType.eq(customSearchCommand.getAnimalType()));
return visits.fetch();
}
CustomSearchCommand is an object that provides parameters for filtering. This is class:
public class CustomSearchCommand {
#Size(min = 3, max = 20)
private String medicalSpecialization;
private AnimalType animalType;
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateFrom;
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
private LocalDateTime dateTo;
public String getMedicalSpecialization() {
return medicalSpecialization;
}
public AnimalType getAnimalType() {
return animalType;
}
public LocalDateTime getDateFrom() {
return dateFrom;
}
public LocalDateTime getDateTo() {
return dateTo;
}
}
My question is, How can I change the method of filtering visits so that I can filter visits when the user provides, for example, only the date from and animal type or, for example, only the date from and medical specialization. The point is that the CustomSearch command object may have, for example, the medical specialisation field as null, and then it is to filter the visits by the rest of the parameters.
What is yours advice? How can I fix this? Thank you for your help
I need to deserialize the Json to Java Objects in Junit. I have Json file like
{
"studentId":57,
"JoinedDate":"31-12-2019",
"DOB":"08-06-1998"
}
I have class for the same to map
public class Student{
private long studentId ;
private LocalDate JoinedDate;
private LocalDate DOB ;
public long getStudentId() {
return studentId;
}
public void setStudentId(long studentId) {
this.studentId = studentId;
}
public LocalDate getJoinedDate() {
return JoinedDate;
}
public void setJoinedDate(LocalDate joinedDate) {
JoinedDate = joinedDate;
}
public LocalDate getDOB() {
return DOB;
}
public void setDOB(LocalDate dOB) {
DOB = dOB;
}
I need to write centralized builder for Unit testing project similar like this
builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
Main Class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public class Main{
#Test
public void contextLoads() {
assertTrue(true);
}
}
Unit testing Project looks like
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public class StudentTest{
private ObjectMapper jsonObjectMapper;
#Before
public void setUp() throws IOException {
jsonObjectMapper = new ObjectMapper();
studentJson = IOUtils.toString(getClass().getResourceAsStream(CommonTestConstants.StudentPath+ "/Student.json"));
}
I'm getting a error while mapping the objects -
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.time.LocalDate from String "31-12-2019": Failed to deserialize java.time.LocalDate:
Another Error - Sometimes.
com.fasterxml.jackson.databind.JsonMappingException: Text '31-12-2019'
could not be parsed at index 0
I assume LocalDate format mismatch is the issue. Any suggestion to make it centralized way instead of specifying the format above the fields. Any one please advise?
Reference - Spring Boot JacksonTester custom serializer not registered
You just need to specify the date format by default jackson allows format of yyyy-MM-dd
public class Student{
private long studentId ;
#JsonProperty("JoinedDate") #JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate JoinedDate;
#JsonProperty("DOB") #JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate DOB ;
public long getStudentId() {
return studentId;
}
public void setStudentId(long studentId) {
this.studentId = studentId;
}
public LocalDate getJoinedDate() {
return JoinedDate;
}
public void setJoinedDate(LocalDate joinedDate) {
this.JoinedDate = joinedDate;
}
public LocalDate getDOB() {
return DOB;
}
public void setDOB(LocalDate dOB) {
this.DOB = dOB;
}
I hope it helps you
Springboot 1.4.x or above has this interface Jackson2ObjectMapperBuilderCustomizer which allows you to initialize objectMapper.
What we need to do, is override customize method and register deserializers and
serializers.
#SpringBootApplication
public class TestApplication implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
// pattern could be anything whatever is required
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/dd/MM");
LocalDateSerializer localDateDeserializer = new LocalDateSerializer(formatter);
jackson2ObjectMapperBuilder
.failOnEmptyBeans(false)
.deserializersByType(new HashMap<Class<?>, JsonDeserializer<?>>(){{
put(LocalTime.class, localTimeSerializer);
}});
}
}
We can also add seriliazers similar way.
jackson2ObjectMapperBuilder
.failOnEmptyBeans(false)
.serializersByType(new HashMap<Class<?>, JsonSerializer<?>>(){{
put(LocalTime.class, localTimeSerializer);
}});
you can check more details here. Spring Jackson builder
I'm switching from MongoDB to DynamoDB on a project. Now I'm trying to store this Post object in the db. I'm using the DynamoDBTypeConverter to convert the ZonedDateTime to a String, as DynamoDB doesn't support ZonedDateTime.
That works fine, but when I'm adding a ZonedDateTime field in the Comment object and try to convert it too it doesn't work. I've tried adding a converter to the Comment class, and tried using the converter in the Post class for Comment, but nothing seems to work. Is there a way to convert a field in a nested object for DynamoDB?
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: Cannot marshall type class java.time.ZonedDateTime without a custom marshaler or #DynamoDBDocument annotation.
#DynamoDBTable(tableName = "Post")
public class Post {
#DynamoDBHashKey
private String postNumber;
private ZonedDateTime date;
private List<Comment> comments;
#DynamoDBTypeConverted(converter = ZonedDateTimeConverter.class)
#DynamoDBAttribute
public ZonedDateTime getDate() {
return date;
}
#DynamoDBAttribute(attributeName = "comments")
public List<Comment> getComments() {
return comments;
}
static public class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
#Override
public String convert(final ZonedDateTime time) {
return time.toString();
}
#Override
public ZonedDateTime unconvert(final String stringValue) {
return ZonedDateTime.parse(stringValue);
}
}
#DynamoDBDocument
public class Comment {
private String commentNumber;
#NotNull
private User user;
private ZonedDateTime date;
#DynamoDBTypeConverted(converter = ZonedDateTimeConverter.class)
#DynamoDBAttribute
public ZonedDateTime getDate(){
return this.date;
}
static public class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
#Override
public String convert(final ZonedDateTime time) {
return time.toString();
}
#Override
public ZonedDateTime unconvert(final String stringValue) {
return ZonedDateTime.parse(stringValue);
}
}
I am using Spring Hibernate framework. And I have a problem in passing date as json object. Whenever I try to insert an object, it says error 400, request syntactically incorrect.
My controller class
#RequestMapping(value="/hospital", method= RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody Status addHospitalInfo(#RequestBody HospitalInformation hospitalInformation){
try{
if(hospitalService.addHospitalInfo(hospitalInformation)){
return new Status(1,"Success");
}else{
return new Status(0,"Failed");
}
}catch(Exception e){
e.printStackTrace();
return new Status(0,e.getMessage());
}
}
My domain class is
private Integer hospitalId;
private String shortName;
private String name;
private Integer packageId;
private Date implementationDate;
private Date validFrom;
private Date validUpTo;
public enum SubscriptionType{Free,Complimentary,Paid}
private Integer totalUsers;
private Package packages;
public enum Status{Active,Inactive}
private SubscriptionType subscriptionType;
private Status status;
//normal getters and setters for other fields
#Column(name = "implementation_date",
nullable = false)
public Date getImplementationDate() {
return implementationDate;
}
public void setImplementationDate(Date implementationDate)
{
this.implementationDate = implementationDate;
}
#Column(name = "valid_from",
nullable = false)
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom)
{
this.validFrom =validFrom;
}
#Column(name = "valid_upto",
nullable = false)
public Date getValidUpTo() {
return validUpTo;
}
public void setValidUpTo(Date validUpTo)
{
this.validUpTo =validUpTo;
}
My Dao is
#Transactional
public boolean addHospitalInfo(HospitalInformation hospitalInformation)
throws Exception {
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
if(findByPackageId(hospitalInformation.getPackageId())== null){
return false;
}
else{
session.save(hospitalInformation);
tx.commit();
session.close();
return true;
}
}
#Transactional
public Package findByPackageId(Integer packageId) throws Exception {
Session session=sessionFactory.openSession();
Transaction tx= session.beginTransaction();
List<Package> package1= new ArrayList<Package>();
package1=session
.createQuery("from Package where packageId=?")
.setParameter(0, packageId)
.list();
if (package1.size() > 0) {
return package1.get(0);
} else {
return null;
}
}
And my service class just saves the object into database. So I need help on how to pass date as json object. Thankyou in advance.
To fix your issue you can do one of the two things:
Either use a format that Jackson already recognizes ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd")
or
write a custom deserializer e.g. for yyyyMMdd
public class YourDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String date = jp.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
and annotate your date fields like
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validFrom;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validUpTo;
Serialization
To have your dates printed the way you want in your JSON response, you can write a custom JSON serializer and annotate the fileds with it, so for yyyyMMdd something like
public class YourDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
jgen.writeString(format.format(value));
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
and than annotate your field, e.g.
#JsonDeserialize(using = YourDateSerializer.class)
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
Global config
Note also that you can configure your custom serializers to take effect globally, by customizing Jackson's ObjectMapper that is in charge for the conversion. So something like
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("JsonDateModule", new Version(2, 0, 0, null, null, null));
module.addSerializer(Date.class, new YourDateSerializer());
module.addDeserializer(Date.class, new YourDateDeserializer());
registerModule(module);
}
}
you would need to register your CustomObjectMapper with spring. If you're using the XML config, it would be something like
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="your.package.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I have to convert the incoming parameter value to Repository interface into desired format, is it possible to do it. My Domain Class,
#DynamoDBTable(tableName = "test")
public class Test implements Serializable{
#Id
private String id;
private String name;
private String date;
#DynamoDBHashKey(attributeName = "id")
#DynamoDBAutoGeneratedKey
public String getId() {
return id;
}
#DynamoDBAttribute(attributeName = "name")
public String getName() {
return name;
}
#DynamoDBAttribute(attributeName = "date")
#JsonSerialize(using = StringDateSerializer.class)
public String getDate() {
return date;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
#JsonDeserialize(using = StringDateDeserializer.class)
public void setDate(String date) {
this.date = date;
}
}
And my repository interface,
#EnableScan
#RestResource(path="test", rel="test")
public interface TestRepository extends PagingAndSortingRepository<Test, String>{
#RestResource(path="testsearch", rel="test")
public Page<Test> findByNameAndDateLessThan(#Param("name") String name, #Param("date") String date, Pageable pageable);
}
Here I have to convert the incoming date String to time using getTime() method of Java. Is it possible to achieve this without using controller and am not interested in sending from client side because timezone problem may occur.
My Convertors:
public class StringDateSerializer extends JsonSerializer<String> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
#Override
public void serialize(String time, JsonGenerator gen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
Date date = new Date(Long.parseLong(time));
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
public class StringDateDeserializer extends JsonDeserializer<String> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String dateReceived = parser.getText();
Date date = null;
try {
date = dateFormat.parse(dateReceived);
} catch (ParseException e) {
e.printStackTrace();
}
return String.valueOf(date.getTime());
}
}
Here I have to use, GET /test/search/test?name=xx&date=14-06-2014. I need to get all the names with date less than 14-06-2014 and left the datas with or after 14-06-2014.
While POST and GET, I have converted the incoming and outgoing string using JsonSerialize and JsonDeserialize annotations but if I want to fetch any data using finder method its not converting as I thought.
For example, If I save {"name": "Test", "date": "08-10-2014"}, in DB it will be saved by its equivalent time and If I want to search it using 08-10-2014 not the time constant. I am new to springs and I cant find a way for it. Thanks in advance.
What's the reason you use String as the type for the date in the first place. That's quite suboptimal (to phrase it politely) API design.
Spring Data REST support the usage of #DateTimeFormat on query method parameters to turn the String base representation you get from the HTTP request into a Date. So your repository interface might look something like this:
public interface TestRepository extends PagingAndSortingRepository<Test, String>{
public Page<Test> findByNameAndDate(#Param("name") String name,
#Param("date") #DateTimeFormat(iso = ISO.DATE) Date date, Pageable pageable);
}
This will cause Strings like 2014-06-08 to be turned into the appropriate Date.
If I'm understanding your issue correctly, there are two areas of concern - how Spring-Data-Rest handles date mapping, and how Spring-Data-DynamoDB handles date mapping.
With regard to Spring-Data-DynamoDB:
DynamoDB stores dates as Strings, so if you have a date attribute as part of your date model you can either represent them as Strings in your data model ( as I think you are doing currently ), or you can represent them as Dates, and configure Spring-Data-DynamoDB so that it maps the Dates to Strings. This can be done using Custom Marshallers from amazon-aws-sdk, and support has been added to handle this in the Spring Data DynamoDB module.
You can read about marshallers here : http://java.awsblog.com/post/Tx1K7U34AOZBLJ2/Using-Custom-Marshallers-to-Store-Complex-Objects-in-Amazon-DynamoDB
Note that this marshalling is separate from any mapping that you may be requiring Spring-Data-Rest to perform from JSON to objects - for this you will still need the #DateTimeFormat annotation.
If you want to represent the date as a java.util.Date in your data model, simply annotate the getter for the attribute in your domain class with #DynamoDBMarshalling, and pass in the class of marshaller you wish to use, eg:
#DynamoDBRangeKey(attributeName = "ReplyDateTime")
#DynamoDBMarshalling(marshallerClass=DefaultDynamoDBDateMarshaller.class)
public Date getReplyDateTime() {
...
DefaultDynamoDBDateMarshaller here is a support class from Spring-Data-DynamoDB, but you can implement your own for custom date/string mapping.
With this in place, you can now change your repository finder methods so they expect Date parameters rather than Strings:
public Page<Reply> findByReplyDateTimeAfter(Date replyDateTime,Pageable pageable);
Hope this helps,
Cheers,
Michael