How spring InitBinder works? - java

I read about InitBinder on net but not very clear how it works. As per my understanding it can be used to perform cross cutting
concern like setting validator, conversion of request parameter to some custom object etc
Came across below example on net
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
Handler method is
public void handlerMethod(#RequestParam("date") Date date) {
}
The advantage is before DispatcherServlet calls the handlerMethod it converts the request parameter in to Date object (otherwise
developer has to do it handleMethod). Right?
My question how spring knows which request parameter needs to be converted to Date object?
Say my request string is /someHandler/name?user=Brian&userCreatedDate=2011-01-01&code=aaaa-bb-cc
So how spring knows it has to convert userCreatedDate not other two parameters i.e code/user?

It knows which request parameters to apply the conversion to based on their datatype.
By doing this:
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
You are registering the editor for the Date type.
So if you have
#RequestMapping("/foo")
public String foo(#RequestParam("date") Date date,
#RequestParam("name") String name) {
// ...
}
Then the editor will be applied only to the first parameter, because the second one is String not Date.

Related

Date as #QueryParam in JAX-RS RestEasy [duplicate]

I have a service defined as follows.
public String getData(#QueryParam("date") Date date)
I'm trying to pass a java.util.Date to it from my client (which is jaxrs:client of CXF, not a generic HTTP client or browser).
My service receives the date as Thu Mar 01 22:33:10 IST 2012 in the HTTP URL. Since CXF won't be able to create a Date object using this String, my client receives a 404 error.
I tried using a ParameterHandler on the service side, but I still can't parse it successfully because I'm not expecting the date in any specific format.
As per this post, passing a Date is supposed to work out of the box, but I can't seem to get the basic case working. Am I required to do anything in order to successfully pass a Date object from my client to service? Appreciate any help.
Thanks
The problem is that JAX-RS dictates that parameter unbundling be done in one of two ways:
The parameter bean has a public constructor that accepts a String
The parameter bean has a static valueOf(String) method.
In your case, the Date is being unbundled via its Date(String) constructor, which cannot handle the input format your client is sending. You have a couple options available to remedy this:
Option 1
Get your client to change the format of the date before they send it. This is the ideal, but probably the hardest to accomplish!
Option 2
Handle the crazy date format. The options for this are:
Change your method signature to accept a string. Attempt to construct a Date object out of that and if that fails, use your own custom SimpleDateFormat class to parse it.
static final DateFormat CRAZY_FORMAT = new SimpleDateFormat("");
public String getData(#QueryParam("date") String dateString) {
final Date date;
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
Define your own parameter class that does the logic mentioned above. Give it a string constructor or static valueOf(String) method that invokes the logic. And an additional method to get the Date when all is said and done.
public class DateParameter implements Serializable {
public static DateParameter valueOf(String dateString) {
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
private Date date;
// Constructor, Getters, Setters
}
public String getData(#QueryParam("date") DateParameter dateParam) {
final Date date = dateParam.getDate();
}
Or finally, you can register a parameter handler for dates. Where its logic is simply the same as mentioned for the other options above. Note that you need to be using at least CXF 2.5.3 in order to have your parameter handler evaluated before it tries the default unbundling logic.
public class DateHandler implements ParameterHandler<Date> {
public Map fromString(String s) {
final Date date;
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
}
Percepiton's answer was very useful, but ParameterHandler has been deprecated in Apache-cxf 3.0, see the Apache-cxf 3.0 Migration Guide:
CXF JAX-RS ParameterHandler has been dropped, please use JAX-RS 2.0 ParamConverterProvider.
So I add an example with the ParamConverterProvider :
public class DateParameterConverterProvider implements ParamConverterProvider {
#Override
public <T> ParamConverter<T> getConverter(Class<T> type, Type type1, Annotation[] antns) {
if (Date.class.equals(type)) {
#SuppressWarnings("unchecked")
ParamConverter<T> paramConverter = (ParamConverter<T>) new DateParameterConverter();
return paramConverter;
}
return null;
}
}
public class DateParameterConverter implements ParamConverter<Date> {
public static final String format = "yyyy-MM-dd"; // set the format to whatever you need
#Override
public Date fromString(String string) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
return simpleDateFormat.parse(string);
} catch (ParseException ex) {
throw new WebApplicationException(ex);
}
}
#Override
public String toString(Date t) {
return new SimpleDateFormat(format).format(t);
}
}
The #SuppressWarnings is required to suppress an "unchecked or unsafe operations" warning during compilation. See How do I address unchecked cast warnings for more details.
The ParamConverterProvider can be registred as provider. Here is how I did it:
<jaxrs:server id="myService" address="/rest">
<jaxrs:serviceBeans>
...
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="dateParameterConverterProvider" />
</jaxrs:providers>
</jaxrs:server>
<bean id="dateParameterConverterProvider" class="myPackage.DateParameterConverterProvider"/>
See Apache-cxf JAX-RS : Services Configuration for more information.
Using a custom DateParam class seems the safest option. You can then base your method signatures on that and implement the ugly conversion logic inside the valueOf() method or the class constructor. It is also more self-documenting than using plain strings
As #Perception suggests in option two, you can handle the date. But you should use following:
private Date getDateFromString(String dateString) {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = df.parse(dateString);
return date;
} catch (ParseException e) {
//WebApplicationException ...("Date format should be yyyy-MM-dd'T'HH:mm:ss", Status.BAD_REQUEST);
}
}
You call it from within the resource as
Date date = getDateFromString(dateString);//dateString is query param.

Method to handle invalid request in a Java Web Application?

I have to get two dates in a request parameter lets say "from=jan 1 2016" and "to= feb 1 2016". "from" should always come before "to".
My controller methods return Map in response if "from" is before "to", but if "to=jan 1 2016" value comes before "from=feb 1 2016", how do I handle the response to send a message?
The proper way would be to throw an exception if anything happens that shouldn't happen. If you're using Java 8 time API (or something like Joda time), you can easily achieve this by using isBefore():
if (to.isBefore(from)) {
// Write your own exception class
throw new InvalidParameterException("To cannot be before from");
}
Now you can use #ExceptionHandler to do anything you want if an exception is thrown. For example:
#ExceptionHandler(InvalidParameterException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ErrorMessageDTO handleInvalidParameter(InvalidParameterException ex) {
// Write your own DTO to return an exception
return new ErrorMessageDTO(ex.getMessage());
}
If you want to use dates as request parameters, you might want to use a Formatter<LocalDate> to properly do this:
#Component
public class LocalDateStringFormatter implements Formatter<LocalDate> {
// Or use a custom formatter with a custom pattern
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
#Override
public LocalDate parse(String isoDateString, Locale locale) throws ParseException {
return LocalDate.parse(text, FORMATTER);
}
#Override
public String print(LocalDate date, Locale locale) {
retun date.format(FORMATTER);
}
}
This way you can map #RequestParams of type LocalDate.

Thymeleaf seems to prefer toString over initbinder customEditor

I want to pass a LocalDateTime object to thymeleaf with a specific format (yyyy/MM/dd HH:mm) and later receive it back into my controller class.
I want to use an customEditor / initbinder to do the convertion.
/**
* Custom Initbinder makes LocalDateTime working with javascript
*/
#InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(LocalDateTime.class, "reservationtime", new LocalDateTimeEditor());
}
public class LocalDateTimeEditor extends PropertyEditorSupport {
// Converts a String to a LocalDateTime (when submitting form)
#Override
public void setAsText(String text) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
LocalDateTime localDateTime = LocalDateTime.parse(text, formatter);
this.setValue(localDateTime);
}
// Converts a LocalDateTime to a String (when displaying form)
#Override
public String getAsText() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
String time = ((LocalDateTime)getValue()).format(formatter);
return time;
}
}
While spring uses my initbinder when it receives the data from the form, thymeleaf though seems to prefer the .toString() method over my initbinder and my getAsText() method never gets called.
My view:
<input type="text" th:name="${reservationtime}" id="reservationtime" class="form-control"
th:value="${reservationtime}"/>
I find the initbinder "way" quite good in terms of code readability. So I would like to keep using the initbinder. Is it possible to tell thymeleaf to use my initbinder or any other good workaround?
remove the parameter"reservationtime", may resolve the issue :
binder.registerCustomEditor(LocalDateTime.class, new LocalDateTimeEditor());
And Then, the converter will be used for ALL LocalDateTime fields

How to work with Spring based form tag Date field?

In my Spring application have jsp and form.
demo.jsp have one field <form:input path="fromDate"/>
And in my DemoForm have field private Date fromDate;,
When we store the value Null value storing...
My Question Is their any direct tag for Store the date in my spring supplied jsp tag.
other wise give me other alternate way..
You need to define the custom editor for the Date in your controller. Please try below code.
#InitBinder
public void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
df.setLenient(false);
CustomDateEditor editor = new CustomDateEditor(df, true); // second argument 'allowEmpty' is set to true to allow null/empty values.
binder.registerCustomEditor(Date.class, editor);
}

Mapping Java Date Object to XML Schema datetime format

I am having some problem mapping my Java Data Type to standard Schema Date data type.
I have a simple class that I annotated like this. The period instance variable is of Java Date object type.
#XmlAccessorType(value = XmlAccessType.NONE)
public class Chart {
#XmlElement
private double amount;
#XmlElement
private double amountDue;
#XmlElement
private Date period;
//constructor getters and setters
}
Here is my Web Service
#WebService
public class ChartFacade {
#WebMethod
public Chart getChart() throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd");
Chart chart = new Chart(20.0,20.5, df.parse("2001-01-01"));
return chart;
}
}
My problem is it returns the date data in a format not according to what I am expecting.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getChartResponse xmlns:ns2="http://ss.ugbu.oracle.com/">
<return>
<amount>20.0</amount>
<amountDue>20.5</amountDue>
**<period>2001-01-01T00:01:00+08:00</period>**
</return>
</ns2:getChartResponse>
</S:Body>
</S:Envelope>
I wanted the period element to be returned like this
<period>2001-01-01</period>
Is there any way I can achieve this?
You can do the following to control the schema type:
#XmlElement
#XmlSchemaType(name="date")
private Date period;
For More Information:
http://bdoughan.blogspot.com/2011/01/jaxb-and-datetime-properties.html
Use #XmlJavaTypeAdapter annotation and you can marshal/unmarshal your fields any way you want.
Cannot tell though if it's the simplest way.
And note also that it may harm interoperability with any code that would try to use your WSDL. The programmers for that other code would see xsd:string as the field type, and therefore will have to do formatting and parsing manually (just like you do, yes), introducing who knows how many bugs. So please consider if the xsd:date a bad choice really.
Stolen from here:
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
Date someDate;
...
public class DateAdapter extends XmlAdapter<String, Date> {
// the desired format
private String pattern = "MM/dd/yyyy";
public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}
public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}
}
UPDATE: as was mentioned by #Blaise Doughan, a much shorter way is to annotate the date with
#XmlSchemaType("date")
Date someDate;
Despite it is still not clear why timezone information is not generated for the date, this code works in practice and requires much less typing.
Your Chart constructor seems to be parsing the formatted date string back into a Date, which is then being serialized using the default format to the XML response.
I guess using private String period; (and fixing the constructors) should work

Categories