Spring change date input format - spring

I am trying to create a form, that will send back an object with a timestamp. Right now, the input format must be yyyy-MM-dd HH:mm:ss, I want the timestamp to be entered in the format dd.MM.yyyy HH:mm - how can I change the input format?
The object class:
public class Test {
private Timestamp dateStart;
public Timestamp getDateStart() {
return dateStart;
}
public void setDateStart(Timestamp dateStart) {
this.dateStart = new Timestamp(dateStart.getTime());
}
}
The controller method:
#RequestMapping(value="test", method = RequestMethod.POST)
public View newTest(#ModelAttribute("test") Test test, Model model) {
//save the Test object
}
The jsp form:
<form:form action="service/test" method="post" modelAttribute="test">
<form:input type="text" path="dateStart" />
</form:form>
I get this error, when the format isn't right:
Field error in object 'test' on field 'dateStart': rejected value [22.05.2012 14:00]; codes [typeMismatch.test.dateStart,typeMismatch.dateStart,typeMismatch.java.sql.Timestamp,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [test.dateStart,dateStart]; arguments []; default message [dateStart]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.sql.Timestamp' for property 'dateStart'; nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "22.05.2012 14:00" from type 'java.lang.String' to type 'java.sql.Timestamp'; nested exception is java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]]

Thanks to Tomasz I got the answer, I have to add a binder method to the controller:
#InitBinder
public void binder(WebDataBinder binder) {binder.registerCustomEditor(Timestamp.class,
new PropertyEditorSupport() {
public void setAsText(String value) {
try {
Date parsedDate = new SimpleDateFormat("dd.MM.yyyy HH:mm").parse(value);
setValue(new Timestamp(parsedDate.getTime()));
} catch (ParseException e) {
setValue(null);
}
}
});
}

FYI, here is the code for a complete Timestamp custom editor (it supports getAsText() too), courtesy of http://adfinmunich.blogspot.com/2011/04/how-to-write-sqltimestamppropertyeditor.html, just change DEFAULT_BATCH_PATTERN to match your desired date/timestamp pattern, OR send the desired pattern when you construct the SqlTimestampPropertyEditor:
package org.springframework.beans.custompropertyeditors;
import java.beans.PropertyEditorSupport;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Property editor for java.sql.Timestamp, supporting SimpleDateFormat.
*
* Using default Constructor uses the pattern yyyy-MM-dd
* Using the constructor with String, you can use your own pattern.
*
*/
public class SqlTimestampPropertyEditor extends PropertyEditorSupport {
public static final String DEFAULT_BATCH_PATTERN = "yyyy-MM-dd";
private final SimpleDateFormat sdf;
/**
* uses default pattern yyyy-MM-dd for date parsing.
*/
public SqlTimestampPropertyEditor() {
this.sdf = new SimpleDateFormat(SqlTimestampPropertyEditor.DEFAULT_BATCH_PATTERN);
}
/**
* Uses the given pattern for dateparsing, see {#link SimpleDateFormat} for allowed patterns.
*
* #param pattern
* the pattern describing the date and time format
* #see SimpleDateFormat#SimpleDateFormat(String)
*/
public SqlTimestampPropertyEditor(String pattern) {
this.sdf = new SimpleDateFormat(pattern);
}
/**
* #see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
*/
#Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(new Timestamp(this.sdf.parse(text).getTime()));
} catch (ParseException ex) {
throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
}
}
/**
* Format the Timestamp as String, using the specified DateFormat.
*/
#Override
public String getAsText() {
Timestamp value = (Timestamp) getValue();
return (value != null ? this.sdf.format(value) : "");
}
}
To use this class, you would define the following #InitBinder:
#InitBinder
public void binder(WebDataBinder binder) {binder.registerCustomEditor(Timestamp.class,
new org.springframework.beans.custompropertyeditors.SqlTimestampPropertyEditor();}
If you want to use a non-default date/timestamp pattern, supply it in the constructor to the SqlTimestampPropertyEditor, so for this particular example you could use:
#InitBinder
public void binder(WebDataBinder binder) {binder.registerCustomEditor(Timestamp.class,
new org.springframework.beans.custompropertyeditors.SqlTimestampPropertyEditor("dd.MM.yyyy HH:mm");}

Related

How do I add a Type to a graphql-java-annotations project?

The documentation for graphql-java-annotations doesn't do such a great job of telling me how to add a Custom Scalar to my schema: https://github.com/Enigmatis/graphql-java-annotations/tree/v8.0.1#annotations-schema-creator
What I need is to create some 'scalar Date' in the Schema. It is unclear how to do this with the AnnotationsSchemaCreator builder thing.
GraphQLSchema schema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(Query.class) // to create you query object
.mutation(Mutation.class) // to create your mutation object
.subscription(Subscription.class) // to create your subscription object
.directive(UpperDirective.class) // to create a directive
.additionalType(AdditionalType.class) // to create some additional type and add it to the schema
.typeFunction(CustomType.class) // to add a typefunction
.setAlwaysPrettify(true) // to set the global prettifier of field names (removes get/set/is prefixes from names)
.setRelay(customRelay) // to add a custom relay object
.build();
The docs give me just that. Is a typeFunction what I need here? Do I have to first get the graphql-java "Custom Scalar" stuff set up and put that into the typeFunction?
What's happening right now is that my graphql-java-annotations Types which need the Date type...
public abstract class BasePart {
#GraphQLField
#GraphQLNonNull
#JsonIgnore
public String id;
...
#GraphQLField
public Date createdOn;
...
}
Get into the Schema without the Date scalar defined so the GraphiQL UI is rejecting it with errors like...
Error: Date fields must be an object with field names as keys or a function which returns such an object.
at invariant (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:13:12678)
at defineFieldMap (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:16395)
at e.getFields (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:14:22028)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22055
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22200
at Array.forEach (<anonymous>)
at http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22082
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:22227)
at typeMapReducer (http://localhost.blueorigin.com:8080/webjars/graphiql/0.10.1/graphiql.min.js:15:21564)
I'm trying to figure out how to get that information into the, what, AnnotationsSchemaCreator.newAnnotationsSchema() builder?
How do you add a Custom Scalar to a graphql-java-annotations project?
The TypeFunction is the key. You pass the TypeFunction when you are building the Schema with the AnnotationsSchemaCreator. The following code effectively got scalar Date into the service's GraphQL Schema
graphQLSchema = AnnotationsSchemaCreator.newAnnotationsSchema()
.query(QuerySchema.class)
.setAlwaysPrettify(true)
.typeFunction(new MyDateTypeFunction()) // <-- This got scalar Date onto the schema
.build();
The TypeFunction itself realizes the support for the scalar Date.
public class MyDateTypeFunction implements TypeFunction {
#Override
public boolean canBuildType(Class<?> clazz, AnnotatedType annotatedType) {
return clazz == java.util.Date.class;
}
#Override
public GraphQLType buildType(
boolean b,
Class<?> clazz,
AnnotatedType annotatedType,
ProcessingElementsContainer processingElementsContainer) {
return MY_DATE;
}
public static final GraphQLScalarType MY_DATE = GraphQLScalarType
.newScalar()
.name("Date")
.description("Coerce java.util.Date to/from a String representation of the long value of getTime().")
.coercing(
new Coercing() {
#Override
public Object serialize(Object dataFetcherResult) throws CoercingSerializeException {
if (dataFetcherResult instanceof Date) {
final String result = String.format("%d", ((Date) dataFetcherResult).getTime());
return result;
}
final String message =
String.format("Expected type java.util.Date but found %s", typeName(dataFetcherResult));
throw new CoercingSerializeException(message);
}
#Override
public Object parseValue(Object input) throws CoercingParseValueException {
if (input instanceof String) {
try {
return stringToDate((String) input);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseValueException(message);
}
}
final String message = String.format("Unable to parseValue %s to a java.util.Date", input);
throw new CoercingParseValueException(message);
}
#Override
public Object parseLiteral(Object input) throws CoercingParseLiteralException {
if (input instanceof StringValue) {
try {
final String inputStringValue = ((StringValue) input).getValue();
return stringToDate(inputStringValue);
} catch (NumberFormatException nfe) {
final String message = String.format("NumberFormatException %s", nfe.getMessage());
throw new CoercingParseLiteralException(message);
}
}
final String message = String.format("Unable to parseLiteral %s to a java.util.Date", input);
throw new CoercingParseLiteralException(message);
}
}
)
.build();
public static Date stringToDate(String input) throws NumberFormatException {
final long inputAsLong = Long.parseLong(input);
return new Date(inputAsLong);
}
public static String typeName(Object input) {
return input == null ? "null" : input.getClass().getName();
}
}
Note that I'm not recommending that you represent java.util.Date as the String value of the long getTime(), java.time.Instant's ISO-8601 is so much more readable, but my service needed this string value and this is how I got it into a graphql-java-annotation's project schema.

input date error with thymeleaf

I have a html code:
<input type="date" th:field="*{birthday}"/>
When I submit the form I get an error:
Failed to convert property value of type [java.lang.String] to required type [java.util.Calendar] for property birthday; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [java.util.Calendar] for property birthday: no matching editors or conversion strategy found
I'm using Spring. How can I fix?
Thanks.
The controller:
#RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
public String updateMember(Model model,
#Valid Member member,
BindingResult bindingResult,
#RequestParam(value="action", required=true) String action) {
System.out.println("updateMember POST - start");
System.out.println(member);
if (bindingResult.hasErrors()) {
System.out.println("bindingResult.hasErrors");
return "member_edit";
}
memberService.update(member);
// todo verificar se precisa dessa linha mesmo chamando o redirect
model.addAttribute("members", memberService.getAll());
return "redirect:/";
}
Spring don't understand the "birthdate" field in java.lang.String because in your Bean it's a java.util.Calendar.
You need to configure the converter. Something like that :
#Configuration
public class ConversionServiceConfig extends WebMvcConfigurerAdapter {
#Bean
public CalendarFormatter calendarFormatter() {
return new CalendarFormatter();
}
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(calendarFormatter());
}
}
And your CalendarFormatter looks like this :
public class CalendarFormatter implements Formatter<Calendar> {
final String defaultDateFormat = "dd.MM.yyyy";
#Override
public String print(Calendar object, Locale locale) {
return new SimpleDateFormat(defaultDateFormat).format(object.getTime());
}
#Override
public Calendar parse(String text, Locale locale) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(defaultDateFormat);
Date date = sdf.parse(text);
return sdf.getCalendar();
}
}

Spring/initBinder - Not able to get the value binded when multiple values are selected

The below code works fine when I try to save a single value of miniApple. I can see the value of miniApple object value in argument of controller i.e., apple. However I am not able to binder if there are multiple values. Not sure what should be the syntax to write inside the initBinder method for converting the string (comma separated value) into List of custom objects(here List of MiniApple)
When I select multiple values, I can the text value in setAsText() as comma separated values( eg: "miniApple1, miniApple2,miniAPlle3")
Controller method save()
public ModelAndView save(#ModelAttribute("Apple") Apple apple, BindingResult result, SessionStatus status) {
}
Init Binder method
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(MiniApple.class, new MiniAppleEditor(this.miniAppleService, true));
}
Custom Editor class with setAsText method
public class MiniAppleEditor extends PropertyEditorSupport {
/** The mini apple service. */
private final MiniAppleService miniAppleService;
/** Whether or not to allow null values. */
private boolean allowEmpty;
/**
* #param miniAppleService the miniAppleService to set
*/
public MiniAppleEditor(MiniAppleService miniAppleService) {
this(miniAppleService, false);
}
/**
* #param miniAppleService the miniAppleService to set
* #param allowEmpty indicates to allow empty mini apple.
*/
public MiniAppleEditor(MiniAppleService miniAppleService, boolean allowEmpty) {
this.miniAppleService = miniAppleService;
this.allowEmpty = allowEmpty;
}
/**
* #param text the id representing the MiniApple object.
*/
#Override
public void setAsText(String text) {
if (allowEmpty && !StringUtils.hasLength(text)) {
setValue(null);
} else {
setValue(miniAppleService.getMiniApple(Integer.parseInt(text)));
}
}
/**
* #return the Id as a String
*/
#Override
public String getAsText() {
MiniApple miniApple = (MiniApple) getValue();
if (miniApple == null) {
return "";
} else {
return miniApple.getAppleName();
}
}
}
Object which will get the values binded as an argument of controller save () method
#Entity
#Table("tbl_apple")
public class Apple implements Serializable {
private MiniApple miniApple;
getter/setter;
}

Spring InitBinder: bind empty or null values of a float field as 0

I'm just wondering if it's possible to tell an #InitBinder that empty float values in a form would be converted to 0.
I know that float is a primitive data type but I'd still like to convert null or empty values to 0.
If that is possible, how can i achieve that?
Otherwise I'll just make a workaround using a String instead of a float
Define a subclsss of CustomNumberEditor as
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.util.StringUtils;
public class MyCustomNumberEditor extends CustomNumberEditor {
public MyCustomNumberEditor(Class<? extends Number> numberClass) throws IllegalArgumentException {
super(numberClass, true);
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (!StringUtils.hasText(text)) {
setValue(0);
}else {
super.setAsText(text.trim());
}
}
}
Then in your controller class (I create a BaseController for all my application controllers, I need this behavior for all the numeric primitive types in my application, so I simply define this in my BaseController), register binders for the various primitive types.
Note that the constructor parameter of MyCustomNumberEditor must be a subclass of Number, instead of primitive class type.
#InitBinder
public void registerCustomerBinder(WebDataBinder binder) {
binder.registerCustomEditor(double.class, new MyCustomNumberEditor(Double.class));
binder.registerCustomEditor(float.class, new MyCustomNumberEditor(Float.class));
binder.registerCustomEditor(long.class, new MyCustomNumberEditor(Long.class));
binder.registerCustomEditor(int.class, new MyCustomNumberEditor(Integer.class));
....
}
Yes you could always do that .Spring have a CustomNumberEditor which is a customizable property editor for any Number subclass like Integer, Long, Float, Double.It is registered by default by BeanWrapperImpl,but, can be overridden by registering custom instance of it as custom editor.It means you could extend a class like this
public class MyCustomNumberEditor extends CustomNumberEditor{
public MyCustomNumberEditor(Class<? extends Number> numberClass, NumberFormat numberFormat, boolean allowEmpty) throws IllegalArgumentException {
super(numberClass, numberFormat, allowEmpty);
}
public MyCustomNumberEditor(Class<? extends Number> numberClass, boolean allowEmpty) throws IllegalArgumentException {
super(numberClass, allowEmpty);
}
#Override
public String getAsText() {
//return super.getAsText();
return "Your desired text";
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
super.setAsText("set your desired text");
}
}
And then register it normally in you controller:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Float.class,new MyCustomNumberEditor(Float.class, true));
}
This should do the task.

Autocomplete using advanced model in wicket

First I want to say I saw a site:
https://cwiki.apache.org/WICKET/autocomplete-using-a-wicket-model.html
Still I have some problem with implementation.
Context
I have form where want to edit Order from my database.
I want to autocomplete client's name in form and save it to database.
Problem
I can see suggestions of client names and I can choose which client's name I'll use.
Just when I submit form new client won't be saved in order, in database.
I don't know how to relate form Model with AutoCompleteTextField input.
Any ideas ?
Pseudo code of my classes:
Order{
Long id;
String date;
Client client;
Status status;
...
}
Client{
Long id;
String name;
String nip;
String address;
String postcode;
String city;
String phone;
String mail;
...
}
Status{
Long id;
String name;
String value;
}
Edited:
Yes you are right.
My implementation of AbstractAutoCompleteTextField from site:
AbstractAutoCompleteRenderer autoCompleteRenderer = new AbstractAutoCompleteRenderer() {
protected final String getTextValue(final Object object) {
Client client = (Client) object;
return client.getName();
}
protected final void renderChoice(final Object object, final Response response, final String criteria) {
response.write(getTextValue(object));
}
};
// textfield
AbstractAutoCompleteTextField<Client> name = new AbstractAutoCompleteTextField<Client>("name", new PropertyModel(order, "client"), autoCompleteRenderer) {
protected final List<Client> getChoiceList(final String input) {
return clientService.findByNames(10, 0, input);
}
protected final String getChoiceValue(final Client choice) throws Throwable {
return choice.getId().toString();
}
};
form.add(name);
My form implementation you asked for:
form = new Form("orderForm", new CompoundPropertyModel(order)) {
#Override
public void onSubmit() {
orderService.update((Order) getDefaultModelObject());
setResponsePage(OrdersPage.class);
// Form validation successful. Display message showing edited
// model.
}
};
With this code I got: "'Hurtownia Techniczna "ADA"' is not proper Client." (translated from Polish) in feedback panel.
I think, that's where things go wrong:
AbstractAutoCompleteTextField<Client> name = new AbstractAutoCompleteTextField<Client>("name", new PropertyModel(order, "client"), autoCompleteRenderer)
Rest of the answer edited to reflect the correct use case
To be specific: You're creating a PropertyModel of your orders client value, which is a Client-Object and tie it to a TextField. To create a Client-object from the TextField, wicket needs a converter. There are lots of build-in converters but none of them is capable of converting to a custom object. Thus you'll need to provide the converter by implementing IConverter. Since I don't know how you store and retrieve your Client-objects I can only show you a generic example, a Locale-Converter used in one of my projects:
public class LocaleConverter implements IConverter {
private static final long serialVersionUID = 3251433094703013493L;
/* (non-Javadoc)
* #see org.apache.wicket.util.convert.IConverter#convertToObject(java.lang.String, java.util.Locale)
*/
#Override
public Object convertToObject(String value, Locale locale) {
Locale retValue = null;
try {
retValue = LocaleUtils.toLocale(value);
} catch (IllegalArgumentException e) {
throw (new ConversionException("" + value + " is not a valid locale.", e));
}
return retValue;
}
/* (non-Javadoc)
* #see org.apache.wicket.util.convert.IConverter#convertToString(java.lang.Object, java.util.Locale)
*/
#Override
public String convertToString(Object value, Locale locale) {
return value.toString();
}
}
Then you'll need to register your new converter to your application. Again, this will be slightly different for you since you've got a different converter...
#Override
protected IConverterLocator newConverterLocator() {
ConverterLocator newConverterLocator = (ConverterLocator) super.newConverterLocator();
newConverterLocator.set(Locale.class, new LocaleConverter());
return newConverterLocator;
}
This method needs to be included in your application class.

Resources