0

Lightadmin for timestamp fields such as:

@Temporal(TemporalType.TIMESTAMP)
@Column(name="started_at")
Date startedAt;

does not format them but shows them as the number of milliseconds since the epoch, e.g. 1398940456150.

When you enter a Lightadmin edit page e.g. http://localhost:8080/admin/domain/user/1/edit the values which the form is actually populated with are received in another request - http://localhost:8080/admin/rest/user/1/unit/formView?_=1401699535260, which returns JSON with:

...
"startedAt" : {
    "name" : "startedAt",
    "title" : "started at timestamp",
    "value" : 1398940456150,
    "type" : "DATE",
    "persistable" : true,
    "primaryKey" : false
}
...

The task is to change 1398940456150 to e.g. 01.05.2014 10:34:16.

According to my investigation, org.lightadmin.core.rest.DynamicRepositoryRestController.entity() is the entry point of such requests, the code that is responsible for generating JSON is inside: org.springframework.data.rest.webmvc.RepositoryAwareMappingHttpMessageConverter.writeInternal():

try {
  mapper.writeValue(jsonGenerator, object);
} catch(IOException ex) {
  throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}

mapper is an instance of org.codehaus.jackson.map.ObjectMapper.ObjectMapper, initialized with defaults. If it were possible to add these two lines:

SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.getSerializationConfig().setDateFormat(df);

it would do the job, the question is how this can be done?

Adam Siemion
  • 15,569
  • 7
  • 58
  • 92
  • http://stackoverflow.com/questions/9576907/where-do-i-specify-jackson-serializationconfig-feature-settings-in-spring-3-1 – NimChimpsky Jun 02 '14 at 14:47
  • It does not work, because spring data rest webmvc version 1.0.0.RELEASE does not use `MappingJacksonHttpMessageConverter` but `RepositoryAwareMappingHttpMessageConverter` which has its own `ObjectMapper` instance. – Adam Siemion Jun 02 '14 at 15:21
  • just adapt the code for that then ? – NimChimpsky Jun 02 '14 at 15:22
  • You mean to introduce this small code change into spring data rest webmvc, release it is a new version and set a dependency inside lightadmin to this newly created spring data rest webmvc version? – Adam Siemion Jun 02 '14 at 15:26
  • I am not sure of the best solution, RespsitoryAwareHttpMessageConverter contains a final non injected reference to mapper, this is very not like spring – NimChimpsky Jun 02 '14 at 15:45
  • I have fixed the issue by modifying spring-data-rest-webmvc, will provide an answer with details here later on. – Adam Siemion Jun 03 '14 at 12:02

3 Answers3

1

I am not clear on this data-rest-webmvc but you could try registering a cusotm converter, like so :

@Component
public class DateConverter implements Converter<String, Date> {


    @Override
    public Date convert(String source) {
        return // do the conversion
    }
}

And register like so :

 <bean id="conversionService"
          class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="com.myapp.DateConverter"/>
            </set>
        </property>
    </bean>
NimChimpsky
  • 46,453
  • 60
  • 198
  • 311
  • Lightadmin uses spring data rest webmvc in version 1.0.0.RELEASE where is no such bean - `No bean named 'jacksonSerializationConfig' is defined` – Adam Siemion Jun 02 '14 at 14:34
  • I don't know what lightadmin is, but thats how you do it spring. – NimChimpsky Jun 02 '14 at 14:35
  • I have never seen that jar reference before, spring-mvc will default to a friendly output set up correctly. lightadmin = heavyadmin ? – NimChimpsky Jun 02 '14 at 14:41
  • It seems so, can you please take a look at the code of this [class](https://fisheye.springsource.org/browse/~br=1.0.0.RELEASE/datarest/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryAwareMappingHttpMessageConverter.java?hb=true) that is used by Light(/Heavy)admin and check if the above solution can be applied here. – Adam Siemion Jun 02 '14 at 14:43
  • config normally sets this for you automatically I think, just checking but look slike chang eyour bean reference to objectMapper ? – NimChimpsky Jun 02 '14 at 14:46
  • spring mvc version is 3.2.4 – Adam Siemion Jun 02 '14 at 14:50
  • @AdamSiemion have you got access to the context definition, the answer I linked to should do it – NimChimpsky Jun 02 '14 at 14:52
1

I posted this fix on Github - but here it is:

I fixed this issue by changing the class DomainTypeResourceModule in the lightadmin code. Here is the updated source code of the class. There may be a better way to fix it - but this was the least intrusive way and it covered both, serialize and deserialize.

package org.lightadmin.core.rest;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.deser.std.StdDeserializer;
import org.codehaus.jackson.map.module.SimpleDeserializers;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.module.SimpleSerializers;
import org.codehaus.jackson.map.ser.std.SerializerBase;
import org.springframework.hateoas.Resource;

public class DomainTypeResourceModule extends SimpleModule {

    private final DomainTypeToResourceConverter domainTypeToResourceConverter;

    public DomainTypeResourceModule(final DomainTypeToResourceConverter domainTypeToResourceConverter) {
        super("DomainTypeResourceModule", Version.unknownVersion());

        this.domainTypeToResourceConverter = domainTypeToResourceConverter;
    }

    @Override
    public void setupModule(final SetupContext context) {
        SimpleSerializers serializers = new SimpleSerializers();
        serializers.addSerializer(DomainTypeResource.class, new DomainTypeResourceSerializer());
        serializers.addSerializer(Date.class, new JsonDateSerializer());

        SimpleDeserializers deserializers = new SimpleDeserializers();
        deserializers.addDeserializer(Date.class, new JsonDateDeserializer());

        context.addDeserializers(deserializers);
        context.addSerializers(serializers);
    }

    private class DomainTypeResourceSerializer extends SerializerBase<DomainTypeResource> {

        protected DomainTypeResourceSerializer() {
            super(DomainTypeResource.class);
        }

        @Override
        public void serialize(DomainTypeResource value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            if (null == value) {
                provider.defaultSerializeNull(jgen);
                return;
            }

            final Resource resource = domainTypeToResourceConverter.convert(value.getResource(), value.getConfigurationUnitType(), value.getFieldMetadatas());

            jgen.writeObject(resource);
        }
    }

    private class JsonDateSerializer extends SerializerBase<Date> {

        protected JsonDateSerializer() {
            super(Date.class);
        }

        @Override
        public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {

            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String formattedDate = date == null ? "" : dateFormat.format(date);

            gen.writeString(formattedDate);
        }

    }

    private class JsonDateDeserializer extends StdDeserializer<Date> {

        protected JsonDateDeserializer() {
            super(Date.class);
        }

        @Override
        public Date deserialize(JsonParser json, DeserializationContext context) throws IOException, JsonProcessingException {

            try {
                if(json.getText() != null && !"".equals(json.getText().trim())) {
                    try {
                        return new Date(Long.parseLong(json.getText()));
                    }
                    catch(NumberFormatException nex){
                        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                        return dateFormat.parse(json.getText());
                    }
                }
                else return null;
            }
            catch (ParseException e){
                return null;
            }
        }

    }

}
tipsytopsy
  • 92
  • 8
1

The latest LightAdmin 1.1.0.BUILD-SNAPSHOT version contains extended data types support and major bug-fixes including timestamps-related.

Please check it out and feel free to ask me any questions.

max-dev
  • 623
  • 6
  • 11