Bind XMLGregorianCalendar field in Spring MVC with velocity - java

I have a model object with an XMLGregorianCalendar field. How can I bind it to an input field?
For string fields I'm using:
#springFormInput("model.object.stringfield" "")
but can't work out the corresponding code for an XMLGregorianCalendar

You may be better off converting the XMLGregorianCalendar to something easier to handle like Calendar or Date before handing it off to the presentation layer.

Here's a solution. It uses jodatime but could probably be changed not to:
For the view (velocity in this case):
#springFormInput("model.object.xmlgregoriancalendar.field" "")
For the controller:
#InitBinder
public void binder(WebDataBinder binder) {
binder.registerCustomEditor(XMLGregorianCalendar.class, new PropertyEditorSupport() {
public void setAsText(String value) {
setValue(createXMLGregorianCalendar(value));
}
public String getAsText() {
return new SimpleDateFormat("dd/MM/yyyy").format(((XMLGregorianCalendar)getValue()).toGregorianCalendar().getTime());
}
});
}
private XMLGregorianCalendar createXMLGregorianCalendar(String date) {
LocalDateTime result = DateTimeFormat.forPattern("dd/MM/yyyy").parseDateTime(date).toLocalDateTime();
return xmlDF().newXMLGregorianCalendar(result.toDateTime().toGregorianCalendar());
}
private static DatatypeFactory xmlDF() {
try {
return DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException ex) {
throw new RuntimeException(ex);
}
}

Related

Java ModelMapper map an object in an object

I have the following DTO and VO:
VO
public class ProjectVO {
private Date fechaInicio;
private Date fechaFin;
}
DTO
public class ProjectDTO {
private String fechaInicio;
private String fechaFin;
}
And the following converter to convert the strings to dates:
Converter<String, Date> dateConverter = new Converter<String, Date>()
{
public Date convert(MappingContext<String, Date> context)
{
Date date;
try {
date = new SimpleDateFormat("dd/MM/yyyy").parse(context.getSource());
} catch (ParseException e) {
throw new DateFormatException();
}
return date;
}
};
modelMapper.addConverter(dateConverter);
If I convert a single String to a date using modelmapper it'll work perfectly with this converter.
But now I need to convert my ProjectDTO object to a ProjectVO one and I am getting an error saying that it cannot convert a String to a Date. I suspect this is because the Date is inside the Project object. Am I right? How can I solve this?
Thanks.
Okay it works like a charm and automatically detects it if I use this:
modelMapper.createTypeMap(String.class, Date.class);
and then add the custom converter I created :)

Ideal way to pass date from angular datepicker to java backend?

I'm learning the basics of angular with a simple todo app. I have a simple spring boot backend which works fine. I'm currently struggling the best way to pass the date from a angular bootstrap datepicker to the backend. As the code is now the datatypes do not match.
Is the ideal way to convert it to seconds and convert the seconds back to a java date on the backend?
My ToDo entity:
#Id
#GeneratedValue
private long id;
private #NonNull
String taskName;
private Date dueDate;
private String extraNote;
private boolean taskCompleted;
Where I get the user input when he creates a new todo:
#Input() toDoData = { taskName: '', taskCompleted: false, extraNote: '', dueDate: Date};
addToDo() {
this.todoService.addToDo(this.toDoData).subscribe((result) => {
this.todoService.addToDo(this.toDoData);
});
}
Add todo part of my todoService:
addToDo(todo): Observable<any> {
console.log(todo);
return this.http.post<any>(this.API + 'todos', JSON.stringify(todo), this.httpOptions).pipe(
tap((todo) => console.log(`added todo w/ id=${todo.id}`)),
catchError(this.handleError<any>('addTodo'))
);
}
Thanks for any help!
EDIT (Added ToDoController):
#RestController
public class ToDoController {
private ToDoRepository repository;
public ToDoController(ToDoRepository repository) {
this.repository = repository;
}
#GetMapping("/todos")
List<ToDo> all() {
return repository.findAll();
}
#PostMapping("/todos")
ToDo newToDo(#RequestBody ToDo newToDo) {
return repository.save(newToDo);
}
#GetMapping("/todos/{id}")
ToDo one(#PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new ToDoNotFoundException(id));
}
#PutMapping("/todos/{id}")
ToDo replaceToDo(#RequestBody ToDo newToDo, #PathVariable Long id) {
return repository.findById(id)
.map(toDo -> {
toDo.setTaskName(newToDo.getTaskName());
toDo.setDueDate(newToDo.getDueDate());
toDo.setExtraNote(newToDo.getExtraNote());
toDo.setTaskCompleted(newToDo.getTaskCompleted());
return repository.save(toDo);
})
.orElseGet(() -> {
newToDo.setId(id);
return repository.save(newToDo);
});
}
#DeleteMapping("/todos/{id}")
void deleteToDo(#PathVariable Long id) {
repository.deleteById(id);
}
#GetMapping("/deleteall")
#CrossOrigin(origins = "http://localhost:4200")
public void deleteAll() {
repository.deleteAll();
}
#GetMapping("/init")
#CrossOrigin(origins = "http://localhots:4200")
public void createDefaults() {
Date date = new Date();
repository.save(new ToDo("PMB", date, false));
repository.save(new ToDo("GMDU", date, false));
repository.save(new ToDo("INMA", date, true));
repository.save(new ToDo("SLGP", date, false));
}
}
First of all. When using dates you should take into account summer/winter time issues and therefore I would suggest to use a LocalDate(Time) class.
That said:
I would create a
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// Mirror the default Spring Boot Jackson settings
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public static class LocalDateSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}
}
public static class LocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
}
}
}
that converts all dates/times etc to milliseconds and from milliseconds back to localDate(Time). You can find plenty examples on the internet.
This acts as a filter for all dates/times that pass the endpoints in your application.
That way you do not need to concern yourself with any conversion issues anymore.
Then you will need to implement a dateToMillisecond and visa versa routine in Angular and use it where you need to convert te dates. You could intercept http traffic and do the same, but that might be a bit more complicated to implement.
Sample
function timeFromMillis(millis) {
if (_.isEmpty(millis)) {
return undefined;
}
const momentTimeStamp = moment.unix(millis / 1000);
if (!momentTimeStamp.isValid()) {
return undefined;
}
return momentTimeStamp.toDate();
}
function timeToMillis(obj) {
if (!_.isEmpty(obj)) {
if (obj instanceof moment) {
return obj.valueOf();
} else if (obj instanceof Date) {
return obj.getTime();
else if (angular.isString(obj)) {
return parseDateString(obj).getTime();
} else {
return angular.undefined;
}
}
}
function parseDateString(dateString) {
if (angular.isDefined(dateString) && dateString) {
return moment(dateString, 'YYYY-MM-DD').toDate();
}
}
you have 2 options using timeStamp as long pasing long from angular to beckend and from back to angular This is the way i prefer because timestamp is unique
Or you can use custom serializer deserializer
public class CustomDateTimeSerializer extends StdSerializer<DateTime> {
private static DateTimeFormatter formatter =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
public CustomDateTimeSerializer() {
this(null);
}
protected CustomDateTimeSerializer(Class<DateTime> t) {
super(t);
}
#Override
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
gen.writeString(formatter.print(value));
}
public class CustomDateTimeDeserializer extends JsonDeserializer<DateTime> {
#Override
public DateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");
DateTime dateTime = formatter.parseDateTime(jsonParser.getText());
return dateTime;
}
}
ps: i have used yoda time in serializer and deserializer
I ran into this and it turned out to be timezone issue. From frontend it was sending json with "2019-04-01" but backend was converting it to "2019-03-31"
A lot of the code is not using java.time so I found adding below to application.properties file to be best solution. I also added global date format, but change timezone to what you need.
spring.jackson.date-format=yyyy-MM-dd
spring.jackson.time-zone=America/Chicago

XmlAdapter for list of dates

I have the following class that includes a list of Date which I want to mashall. I have created the JaxBAdapter for the Date class, but it seems it is not called.
I think the problem is the fact that I'm using a list and not just a Date variable.
Could you give me some hint on how should I code the adapter or annotate the class so each element of the list is mashalled using the adapter?
Will it work for JSON serialization as well? I'm planning to use this classes on my REST webservice.
Root.java
#XmlRootElement(name = "root")
#XmlAccessorType(XmlAccessType.FIELD)
public class Root {
#XmlElementWrapper(name="timePeriods")
#XmlElement(name = "timePeriod")
#JsonProperty(value = "timePeriod")
#XmlJavaTypeAdapter(value = JaxBDateThreadSafeAdapter.class, type = Date.class)
private List<Date> timePeriod;
public Root() {
this(new ArrayList<String>(), new ArrayList<Date>(2));
}
public Root(List<Date> timePeriod) {
this.timePeriod = new ArrayList<Date>(timePeriod);
}
}
JaxBAdapter
public class JaxBDateThreadSafeAdapter extends XmlAdapter<String, Date> {
/**
* Thread safe {#link DateFormat}.
*/
private static final ThreadLocal<DateFormat> DATE_FORMAT_TL =
new ThreadLocal<DateFormat>() {
#Override
protected DateFormat initialValue() {
// return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// ISO 8601 format
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
}
};
#Override
public String marshal(Date v) throws Exception {
return DATE_FORMAT_TL.get().format(v);
}
#Override
public Date unmarshal(String v) throws Exception {
return DATE_FORMAT_TL.get().parse(v);
}
}
The #XmlJavaTypeAdapter works as such with java.util.List as well. But the problem in the adapter class is the date format that is used. JAXB (atleast 2.x onwards) is not strict and dont report such errors and quietly suppresses it.
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
change to
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");

JDBI: Inserting Dates as longs (milliseconds)

In my application I store dates as milliseconds
public class Model {
private long date = System.currentTimeMillis();
public void setDate(long date) {
this.date = date;
}
public long getDate() {
return date;
}
}
I have a JDBI Data Access Object which looks like:
public interface ModelDAO {
#SqlBatch("REPLACE INTO model (date) VALUES (:date)")
#BatchChunkSize(1000)
void insertModels(#BindBean List<Model> models);
#SqlQuery("SELECT * FROM model ORDER BY date DESC")
List<Model> getModels();
}
However when I try to insert I get:
org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException:
java.sql.BatchUpdateException: Data truncation: Incorrect datetime
value: '1430262000000' for column 'date'
Is there a way I can tell JDBI how to convert this without requiring something like the below for all my classes with dates in?
#BindingAnnotation(BindModel.ModelBindingFactor.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface BindModel {
public static class ModelBindingFactor implements BinderFactory {
public Binder build(Annotation annotation) {
return new Binder<BindModel, Model>() {
public void bind(SQLStatement q, BindModel bind, Model model) {
q.bind("date", new Timestamp(model.getDate()));
}
};
}
}
}
I would be willing to switch my models to use a DateTime object if it makes things cleaner.
It will be easier and more cleaner if you can use DateTime to store date values. You should have a argumentFactory to convert DateTime to sql date. You can use the following one.
public class DateTimeAsSqlDateArgument implements ArgumentFactory<DateTime> {
#Override
public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) {
return value != null && DateTime.class.isAssignableFrom(value.getClass());
}
#Override
public Argument build(Class<?> expectedType, final DateTime value, StatementContext ctx) {
return new Argument() {
#Override
public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
statement.setTimestamp(position, new Timestamp(value.getMillis()));
}
};
}
}
Register this argument factory to dbi. That's all you need. JDBI will use this factory whenever it see DateTime object.
dbi.registerArgumentFactory(new DateTimeAsSqlDateArgument());

Jackson JsonDeserialize not being called for #QueryParam

I have mapped a custom deserializer to convert Strings on dd/MM/yyyy pattern to LocalDate so I can call my services with a more readable signature..
This is my dto class that is used as a Jersey #BeanParam to transport data between layers:
public class ProdutoFilterDto implements FilterDto {
private static final long serialVersionUID = -4998167328470565406L;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataInicial;
#QueryParam("dataInicial")
#JsonDeserialize(using = CustomLocalDateDeserializer.class)
private LocalDate dataFinal;
public LocalDate getDataInicial() {
return dataInicial;
}
public void setDataInicial(LocalDate dataInicial) {
this.dataInicial = dataInicial;
}
public LocalDate getDataFinal() {
return dataFinal;
}
public void setDataFinal(LocalDate dataFinal) {
this.dataFinal = dataFinal;
}
}
and this is my custom deserializer:
public class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
final String data = p.getValueAsString();
return (LocalDate) formatter.parse(data);
}
}
Its being used on this jersey service:
#Path("produto")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ProdutoService {
...
#GET
#Path("query")
#Override
public Response query(
#QueryParam("offset") #DefaultValue(value = "0") Integer offSet,
#QueryParam("limit") #DefaultValue(value = "10") Integer limit,
#BeanParam ProdutoFilterDto filter) { ... }
...
}
I am calling like this:
${host goes here}/produto/query?dataInicial=11/09/1992
The problem is that the deserializer method is never called and the bean param variable remains null..
MessageBodyReaders aren't used for #QueryParam. You seem to be expecting the Jackson MessageBodyReader to handle this deserialization, but it doesn't work like that.
Instead you will want to use a ParamConverter, which will need to be registered through a ParamConverterProvider. For example:
#Provider
public class LocalDateParamConverterProvider implements ParamConverterProvider {
final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
#Override
public <T> ParamConverter<T> getConverter(
Class<T> rawType, Type genericType, Annotation[] antns) {
if (LocalDate.class == rawType) {
return new ParamConverter<T>() {
#Override
public T fromString(String string) {
try {
LocalDate localDate = LocalDate.parse(string, formatter);
return rawType.cast(localDate);
} catch (Exception ex) {
throw new BadRequestException(ex);
}
}
#Override
public String toString(T t) {
LocalDate localDate = (LocalDate) t;
return formatter.format(localDate);
}
};
}
return null;
}
}
Now LocalDate will work with #QueryParam and other #XxxParams also.
Some things to note:
If your goal is to parse both your #XxxParams and your JSON body into a bean this will not work. I'm not sure how that would work, but I'm sure it would involve a lot of hacking, and I wouldn't recommend it.
Your cast to (LocalDate) won't work. It's an illegal cast to java.time.format.Parsed. See correct way in code example.
Related to the above point. I was pulling out my hair for a good hour trying to figure out why I was getting a 404, using your parse code. With a 404, the last place I thought to look was in the ParamConverter. But it seems any uncaught exceptions that are thrown in the ParamConverter, will cause a 404. Doesn't make much sense right? The head pounding led me to this, which led me to this, which seems to be a poor specification
"if the field or property is annotated with
#MatrixParam, #QueryParam or #PathParam then an implementation MUST generate an instance of
NotFoundException (404 status) that wraps the thrown exception and no entity
"
Moral of the story: make sure to catch any possible exceptions in the ParamConverter!
See Also:
Good article on ParamConverters

Categories