Note that there are some explanatory texts on larger screens.

plurals
  1. POsmartgwt restdatasource json date validation
    primarykey
    data
    text
    <p>I am using Spring 3.2 MVC Controller and a Spring-WS to create a RESTful web-service. The Spring controller accepts an object files an update to the database correctly and then returns JSON to the front-end. The Spring Context is set for message converts for JSON. I have Unit Tests for these, so I know the Spring Controllers are working and are filing data accordingly.</p> <p>The error, actually a warning, comes when I get the data/JSON back from the web-service:</p> <pre><code>10:05:08.906[ERROR[Phonebook]10:05:08.902:XRP3:WARN:RestDataSource:restUserDS:restUserDS.userBirthDate:value:-99187200000 failed on validator {type:&amp;quot;isDate&amp;quot;,typeCastValidator:true,_generated:true,defaultErrorMessage:&amp;quot;Must be a date.&amp;quot;} com.smartgwt.client.core.JsObject$SGWT_WARN: 10:05:08.902:XRP3:WARN:RestDataSource:restUserDS:restUserDS.userBirthDate: value: -99187200000 failed on validator: {type: &amp;quot;isDate&amp;quot;,typeCastValidator: true,_generated: true,defaultErrorMessage: &amp;quot;Must be a date.&amp;quot;} at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:105) at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71) at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:172) at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessages(BrowserChannelServer.java:293) at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:547) at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:364) at java.lang.Thread.run(Thread.java:662) </code></pre> <p>So, here is my UserDataSource:</p> <pre><code>package com.opensource.restful.client.datasource; import java.util.HashMap; import java.util.Map; import com.google.gwt.core.client.JavaScriptObject; import com.opensource.restful.shared.Constants; import com.smartgwt.client.data.DSRequest; import com.smartgwt.client.data.DSResponse; import com.smartgwt.client.data.OperationBinding; import com.smartgwt.client.data.RestDataSource; import com.smartgwt.client.data.fields.DataSourceBooleanField; import com.smartgwt.client.data.fields.DataSourceDateField; import com.smartgwt.client.data.fields.DataSourceIntegerField; import com.smartgwt.client.data.fields.DataSourceTextField; import com.smartgwt.client.types.DSDataFormat; import com.smartgwt.client.types.DSOperationType; import com.smartgwt.client.types.DSProtocol; import com.smartgwt.client.util.JSOHelper; import com.smartgwt.client.util.JSON; public class UserDataSource extends RestDataSource { private static UserDataSource instance = null; public static UserDataSource getInstance() { if (instance == null) { instance = new UserDataSource("restUserDS"); } return instance; } private UserDataSource(String id) { setID(id); setClientOnly(false); // set up FETCH to use GET requests OperationBinding fetch = new OperationBinding(); fetch.setOperationType(DSOperationType.FETCH); fetch.setDataProtocol(DSProtocol.GETPARAMS); DSRequest fetchProps = new DSRequest(); fetchProps.setHttpMethod("GET"); fetch.setRequestProperties(fetchProps); // set up ADD to use POST requests OperationBinding add = new OperationBinding(); add.setOperationType(DSOperationType.ADD); add.setDataProtocol(DSProtocol.POSTMESSAGE); // =========================================== DSRequest addProps = new DSRequest(); addProps.setHttpMethod("POST"); // addProps.setContentType("application/json"); add.setRequestProperties(addProps); // set up UPDATE to use PUT OperationBinding update = new OperationBinding(); update.setOperationType(DSOperationType.UPDATE); update.setDataProtocol(DSProtocol.POSTMESSAGE); // =========================================== DSRequest updateProps = new DSRequest(); updateProps.setHttpMethod("PUT"); // updateProps.setContentType("application/json"); update.setRequestProperties(updateProps); // set up REMOVE to use DELETE OperationBinding remove = new OperationBinding(); remove.setOperationType(DSOperationType.REMOVE); DSRequest removeProps = new DSRequest(); removeProps.setHttpMethod("DELETE"); remove.setRequestProperties(removeProps); // apply all the operational bindings setOperationBindings(fetch, add, update, remove); init(); } private DataSourceIntegerField userIdField; private DataSourceBooleanField userActiveField; private DataSourceTextField usernameField; private DataSourceTextField passwordField; private DataSourceTextField firstnameField; private DataSourceTextField lastnameField; private DataSourceTextField emailField; private DataSourceTextField securityQuestion1Field; private DataSourceTextField securityAnswer1Field; private DataSourceTextField securityQuestion2Field; private DataSourceTextField securityAnswer2Field; private DataSourceDateField birthdateField; private DataSourceIntegerField positionIdField; protected void init() { setDataFormat(DSDataFormat.JSON); setJsonRecordXPath("/"); // set the values for the datasource userIdField = new DataSourceIntegerField(Constants.USER_ID, Constants.TITLE_USER_ID); userIdField.setPrimaryKey(true); userIdField.setCanEdit(false); userActiveField = new DataSourceBooleanField(Constants.USER_ACTIVE, Constants.TITLE_USER_ACTIVE); usernameField = new DataSourceTextField(Constants.USER_USERNAME, Constants.TITLE_USER_USERNAME); passwordField = new DataSourceTextField(Constants.USER_PASSWORD, Constants.TITLE_USER_PASSWORD); firstnameField = new DataSourceTextField(Constants.USER_FIRST_NAME, Constants.TITLE_USER_FIRST_NAME); lastnameField = new DataSourceTextField(Constants.USER_LAST_NAME, Constants.TITLE_USER_LAST_NAME); emailField = new DataSourceTextField(Constants.USER_EMAIL, Constants.TITLE_USER_EMAIL); securityQuestion1Field = new DataSourceTextField(Constants.USER_SECURITY_QUESTION_1, Constants.TITLE_USER_SECURITY_QUESTION_1); securityAnswer1Field = new DataSourceTextField(Constants.USER_SECURITY_ANSWER_1, Constants.TITLE_USER_SECURITY_ANSWER_1); securityQuestion2Field = new DataSourceTextField(Constants.USER_SECURITY_QUESTION_2, Constants.TITLE_USER_SECURITY_QUESTION_2); securityAnswer2Field = new DataSourceTextField(Constants.USER_SECURITY_ANSWER_2, Constants.TITLE_USER_SECURITY_ANSWER_2); birthdateField = new DataSourceDateField(Constants.USER_BIRTHDATE, Constants.TITLE_USER_BIRTHDATE); positionIdField = new DataSourceIntegerField(Constants.USER_POSITION_ID, Constants.TITLE_USER_POSITION_ID); // positionActiveField = new DataSourceBooleanField(Constants.USER_ACTIVE, Constants.TITLE_USER_ACTIVE); // positionCodeField; // positionDescriptionField; setFields(userIdField, userActiveField, usernameField, passwordField, firstnameField, lastnameField, emailField, birthdateField, securityQuestion1Field, securityAnswer1Field, securityQuestion2Field, securityAnswer2Field, positionIdField); setFetchDataURL(getServiceRoot() + "/userId/{id}"); // works great setAddDataURL(getServiceRoot() + "/create"); setUpdateDataURL(getServiceRoot() + "/update"); setRemoveDataURL(getServiceRoot() + "/remove"); // works great } protected String getServiceRoot() { return "rest/users"; } protected String getPrimaryKeyProperty() { return "userId"; } @Override protected Object transformRequest(DSRequest dsRequest) { System.out.println("UserDataSource: transformRequest: START"); dsRequest.setContentType("application/json"); JavaScriptObject jso = dsRequest.getData(); String jsoText = JSON.encode(jso); System.out.println("UserDataSource: transformRequest: START: jsoText=" + jsoText); // ================================================================================ // String strDob = JSOHelper.getAttribute(jso, Constants.USER_BIRTHDATE); // Date dateDob = JSOHelper.getAttributeAsDate(jso, Constants.USER_BIRTHDATE); // JSOHelper.setAttribute(jso, Constants.USER_BIRTHDATE, dateDob.getTime()); // System.out.println("UserDataSource: transformRequest: START2: jsoText2=" + jsoText); // ================================================================================ // get the user position id which comes from the UI // the name of this field from the UI 'userPositionId' String userPositionId = JSOHelper.getAttribute(jso, Constants.USER_POSITION_ID); // create a small JavaScriptObject to be used for the position // the JSON string would look like {"id":x} x = userPositionId Map mapPositionId = new HashMap(); mapPositionId.put("id", userPositionId); JavaScriptObject jsoPositionId = JSOHelper.convertMapToJavascriptObject(mapPositionId); // This creates the new JSON attribute: // ... , "position":{"id":x} JSOHelper.setAttribute(jso, "position", jsoPositionId); // remove the JSON Attribute: ... , "userPositionId":x JSOHelper.deleteAttribute(jso, Constants.USER_POSITION_ID); String s1 = JSON.encode(jso); System.out.println("UserDataSource: transformRequest: FINISH: s1=" + s1); return s1; // return super.transformRequest(dsRequest); } protected void transformResponse(DSResponse response, DSRequest request, Object data) { System.out.println("UserDataSource: transformResponse: START"); super.transformResponse(response, request, data); System.out.println("UserDataSource: transformResponse: FINISH"); } } </code></pre> <p>I can confirm I am sending data/JSON just fine. I have to make a slight change to add an attribute that I am sending back. And I believe that is the purpose of TransformRequest. The Spring MVC Controller receiving the Update looks like:</p> <pre><code>@RequestMapping(value="/update", method=RequestMethod.PUT,produces="application/json", headers="content-type=application/json") public @ResponseBody UserDTO updateUser(@RequestBody UserDTO user) { System.out.println("UserController: START: updateUser: user=" + user); UserEntity userEntity = service.update(user); UserDTO userDto = Mapping.mappingUser(userEntity); System.out.println("UserController: FINISH: updateUser: userDto=" + userDto); return userDto; } </code></pre> <p>And I can confirm I am getting a valid UserDTO. When I look at the transformResponse:</p> <pre><code>System.out.println("UserDataSource: transformResponse: START"); super.transformResponse(response, request, data); System.out.println("UserDataSource: transformResponse: FINISH"); </code></pre> <p>I get the error on the first println, I haven't even done the super.transformResponse just yet. When I look at the data coming back, this is the JSON I am getting back.</p> <pre><code>{ "userId":1, "userActive":true, "position":{ "id":1, "active":true, "code":"ADMIN", "description":"Administrator" }, "username":"demo", "password":"demo", "otherPassword":null, "userFirstName":"DemoXXX", "userLastName":"DemoXXX", "userEmail":"tom@tomholmes.netXXX", "userSecurityQuestion1":"Meaning of Life?XXX", "userSecurityAnswer1":"42XX", "userSecurityQuestion2":"aaaXX", "userSecurityAnswer2":"bbbXX", "userBirthDate":-99100800000, "contacts":[ { "contactId":2, "userId":1, "prefix":"Mr.", "firstName":"updated_fn", "middleName":null, "lastName":"updated_ln", "suffix":"Jr.", "address1":"123 main street", "address2":"Apt. 456", "city":"Randolph", "state":"MA", "zip":"12345-1234", "companyId":0, "enteredBy":0, "enteredDate":null, "editedBy":0, "editedDate":null, "birthDate":null, "emails":null, "phones":null, "links":null } ], "userPositionId":null } </code></pre> <p>So ... How do I fix my datasource or transformResponse to remove this warning? The JSON appears to be correct, and the only issue is with the "userBirthDate" when it comes back as a long negative number, I presume the milliseconds from the epoch. Is there some change I can make in the JSON/Jackson Mapper to change how the dates are formatted?</p> <p>Thanks for any help!</p> <p>UPDATE 1: The help provided below was helpful, and now I know this is not a SmartGWT or RestDataSource issue and is strictly with how jackson converts a java.util.Date within an object. The conversion changes dates to a negative long number and should have another format. I am using Spring 3.2 and was using the old Jackson 1.9.14. But now, I upgraded to Jackson 2, and my pom.xml now uses:</p> <pre><code>&lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-core&lt;/artifactId&gt; &lt;version&gt;2.1.4&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-databind&lt;/artifactId&gt; &lt;version&gt;2.1.4&lt;/version&gt; &lt;/dependency&gt; &lt;dependency&gt; &lt;groupId&gt;com.fasterxml.jackson.core&lt;/groupId&gt; &lt;artifactId&gt;jackson-annotations&lt;/artifactId&gt; &lt;version&gt;2.1.4&lt;/version&gt; &lt;/dependency&gt; </code></pre> <p>Within my spring-servlext.xml:</p> <pre><code> &lt;context:component-scan base-package="com.opensource.restful" /&gt; &lt;bean id="jsonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"&gt; &lt;property name="supportedMediaTypes" value="application/json"/&gt; &lt;property name="objectMapper"&gt; &lt;bean class="com.fasterxml.jackson.databind.ObjectMapper"&gt; &lt;property name="dateFormat"&gt; &lt;bean class="java.text.SimpleDateFormat"&gt; &lt;constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ssZ"&gt;&lt;/constructor-arg&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"&gt; &lt;property name="messageConverters"&gt; &lt;list&gt; &lt;ref bean="jsonHttpMessageConverter"/&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;mvc:annotation-driven /&gt; </code></pre> <p>I have been Googling for a few hours now and looking for a solution that uses the Jackson2 mapper within the Spring Configuration, and after I make sure I get all the bean definitions correct, the userBirthDate is still coming back as a negative long. I am sure this configuration can be tweaked just a bit to get it the way I want, so the date comes back as the ISO format: yyyy-MM-dd'T'HH:mm:ssZ</p> <p>Thanks for helping me get closer.</p> <p>UPDATE 2: I think I did it. As previously stated, I upgraded to Jackson2 which I understand is already part of Spring 3.2, which is the version of Spring I am using. </p> <p>The spring-servlet.xml that I am using, and which does work looks like:</p> <pre><code>&lt;context:component-scan base-package="com.opensource.restful" /&gt; &lt;mvc:annotation-driven&gt; &lt;mvc:message-converters register-defaults="true"&gt; &lt;bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"&gt; &lt;property name="objectMapper"&gt; &lt;bean class="com.fasterxml.jackson.databind.ObjectMapper"&gt; &lt;property name="dateFormat"&gt; &lt;bean class="java.text.SimpleDateFormat"&gt; &lt;constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ssZ"&gt;&lt;/constructor-arg&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/property&gt; &lt;/bean&gt; &lt;/mvc:message-converters&gt; &lt;/mvc:annotation-driven&gt; &lt;bean id="jsonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"&gt; &lt;property name="supportedMediaTypes" value="application/json"/&gt; &lt;/bean&gt; &lt;bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"&gt; &lt;property name="messageConverters"&gt; &lt;list&gt; &lt;ref bean="jsonHttpMessageConverter" /&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; &lt;bean id="restTemplate" class="org.springframework.web.client.RestTemplate"&gt; &lt;property name="messageConverters"&gt; &lt;list&gt; &lt;ref bean="jsonHttpMessageConverter" /&gt; &lt;/list&gt; &lt;/property&gt; &lt;/bean&gt; </code></pre> <p>I had to add MappingJackson2HttpMessageConverter the second time because, it's referenced in the restTemplate ... but If I could just define it once, that would be fine. So, maybe someone can help me define the spring-servlet.xml better.</p> <p>Anyway, this change works and as a result the JSON date comes back as:</p> <pre><code> "userBirthDate":"1966-11-03T00:00:00-0500" </code></pre> <p>so, that's progress so far. </p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload