Hot questions for Using Joda-Time in spring mvc

Question:

I'm using Spring Framework for my services API and org.joda.time.DateTime for datetime parsing. Specifically, I'm using the ISOFormatter.dateOptionalTimeParser(), which allows users the flexibility to use just the date, or both date and time, which is a requirement.

Believe me, I've seen all these related questions that I can already tell people are going to point me towards, e.g. this and this, etc.

Previously, I was taking the date as String and then processing it using the joda formatter mentioned above in the service layer, but now I want to add request validation in the controller, which means that if the request is syntactically incorrect, the request shouldn't even go to the service layer.

I've tried using multiple variations of @DateTimeFormat(iso = ISO.DATE_TIME), as well as specifying the pattern String in format thing with no luck, whatsoever.

@RequestMapping(value = URIConstants.TEST_URL, method = RequestMethod.GET)
public @ResponseBody String getData(@RequestParam(required = false) DateTime from,
                                    @RequestParam(required = false)  DateTime to)  {
    return dataService.fetchDataFromDB(from, to);
}

What should I do to ensure that the date I get from user complies with the ISO 8601 dateOptionalTime format? Can I maybe apply multiple patterns to implement this?


Answer:

You can also create a converter and that will take care of it. I have used OffsetDateTime in the example below, but that can be easily replaced with LocalDateTime. For a detailed article, refer this url - http://www.baeldung.com/spring-mvc-custom-data-binder

Even I was struggling with this for sometime and it wasn't working. The trick is to use the @Component annotation and did it for me.

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;

import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

@Component
public class OffsetDateTimeConverter implements Converter<String, OffsetDateTime> {

    @Override
    public OffsetDateTime convert(final String source) {

        if (source == null || source.isEmpty()) {
            return null;
        }

        return OffsetDateTime.parse(source, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    }
}

Question:

I'm trying to configure ObjectMapper in my Web application to serialize/deserialise dates presented as Joda's DateTime in ISO 8601 format. I found useful library jackson-datatype-joda and it's module JodaModule so I've added dependency:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-joda</artifactId>
    <version>2.4.4</version>
</dependency>

My Spring configuration:

<bean id="objectMapper"
    class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
    p:indentOutput="true">

    <property name="featuresToDisable">
        <array>
            <util:constant static-field="com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS" />
        </array>
    </property>

    <property name="modulesToInstall" value="com.fasterxml.jackson.datatype.joda.JodaModule" /> 
</bean>

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper" />
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

When I try to serialize this bean

public class Bean {
    private DateTime start = new DateTime();

    public DateTime getStart() { return start; }
    public void setStart(DateTime start) { this.start = start; }        
}

I get following output as long but instead want it to be in ISO 8601 format:

{"start":1418337158933}

I found that JodaModule also preloads if it is found in classpath so it is not neccessary to register it manually (see github repo) but this code is invoked many times during application start.

I think the reason is that ObjectMapper is instantiated in some other place.

UPDATE: The problem was that there was one more file with Spring configuration where ObjectMapper was declared. Both solutions given in answers will work. Cheers!


Answer:

If you create you custom ObjectMapper class like in the code below it will override default objectMapper and you don't need your xml configuration at all:

@Service
public class CustomObjectMapper extends ObjectMapper {
    public CustomObjectMapper() {
        this.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        this.registerModule(new JodaModule());
    }
}

I've added this simple service to the simple spring boot application and got time in the expected format.

Question:

I'm using Joda and Local Date. I created a custom property editor, it receives the correct value from the view, like "23-05-2017" but when I try to parse it I obtain:

LocalDatePropertyEditor - Error Conversione DateTime
java.lang.IllegalArgumentException: Invalid format: "23-05-2017" is malformed at "-05-2017"

This is my custom editor:

public class LocalDatePropertyEditor extends PropertyEditorSupport{
    private final DateTimeFormatter formatter;

    final Logger logger = LoggerFactory.getLogger(LocalDatePropertyEditor.class);   

    public LocalDatePropertyEditor(Locale locale, MessageSource messageSource) {
        this.formatter = DateTimeFormat.forPattern( messageSource.getMessage("dateTime_pattern", new Object[]{}, locale));
    }

    public String getAsText() {
        LocalDate value = ( LocalDate ) getValue();
        return value != null ? new LocalDate( value ).toString( formatter ) : "";
    }

    public void setAsText( String text ) throws IllegalArgumentException {
        LocalDate val;
        if (!text.isEmpty()){
            try{
                val = DateTimeFormat.forPattern("dd/MM/yyyy").parseLocalDate(text);

                setValue(val);
            }catch(Exception e){

                logger.error("Errore Conversione DateTime",e);
                setValue(null);
            }
        }else{
            setValue(null);
        }
    }
}

and inside the controller I registered it:

@InitBinder
    protected void initBinder(final ServletRequestDataBinder binder, final Locale locale) {
        binder.registerCustomEditor(LocalDate.class, new LocalDatePropertyEditor(locale, messageSource));
    }

How can I fix this error?


Answer:

If format of your date is 23-05-2017, then you use wrong pattern. You should use dd-MM-yyyy instead of dd/MM/yyyy.

Question:

This is a little frustrating... I had this working before in my previous projects, but unable to get it working in my new project after debugging for several hours.

Let's assume I have a simple Rest controller that returns Joda's LocalDate:-

@RestController
@RequestMapping(value = "/api")
public final class ApiController {
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<LocalDate> main() {
        return new ResponseEntity<LocalDate>(LocalDate.now(), HttpStatus.OK);
    }
}

By default, when I call http://app/api, I get [2015,10,13]. What I really want is 2015-10-13.

To solve this in my previous project, I got it working with this configuration in spring-servlet.xml:-

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <context:component-scan base-package="test.controller"/>

    <mvc:annotation-driven/>

    <mvc:resources location="/resources/" mapping="/resources/**"/>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <bean id="objectMapper"
          class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
          p:indentOutput="true"
          p:simpleDateFormat="yyyy-MM-dd'T'HH:mm:ss.SSSZ">
    </bean>

    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"
          p:targetObject-ref="objectMapper"
          p:targetMethod="registerModule">
        <property name="arguments">
            <list>
                <bean class="com.fasterxml.jackson.datatype.joda.JodaModule"/>
            </list>
        </property>
    </bean>

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="objectMapper"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>

But, when I do the same thing in my new project, I'm getting [2015,10,13] again instead of 2015-10-13.

I did upgrade some dependencies and I also make sure there's no additional ObjectMapper being loaded.

Here's my current dependency tree... I removed all the things that I don't need:-

How do I configure Spring MVC to return the correct date format in JSON?

Thank you very much.


Answer:

You declared <mvc:annotation-driven> twice. Try removing the first declaration (empty, default config). Probably the message converter you configured in the second <mvc:annotation-driven> is getting overriden by the first declaration (with default message converters).

Question:

I'm using a mix of xml and codebased configuration. One part of my code configuration was ignored. This one:

@Configuration
@EnableWebMvc
public class RestMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(converter());
        super.configureMessageConverters(converters);
    }

    @Bean
    public MappingJackson2HttpMessageConverter converter() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JodaModule());
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setObjectMapper(mapper);

        return converter;
    }
}

I debug the configureMessageConverters() method on startup of the Spring MVC app, and it is executed. Still, my JSON responses in the controllers are not converting Joda LocalDate into a String representation, but gives back the whole object structure.

Why is it ignored?


Answer:

Using a combination of xml and code based configuration, it's easy to end up in a situation where you configure one thing twice, making Spring overwriting previous configurations. In my case, this was due to MVC also being configured in the xml:

<context:component-scan base-package="uk.co.imperatives.billing.rest" />
<mvc:annotation-driven />
<context:annotation-config />

So, in this case, the xml configuration resetted my code configuration. Removing the <mvc:annotation-driven /> fixed the problem. So, this worked:

<context:component-scan base-package="uk.co.imperatives.billing.rest" />
<context:annotation-config />

Question:

I am working in a Spring-MVC application and for password reset function, i am sending an email, which contains the username+Joda Date. Before sending it, I would like to encrypt the content(date+username) in such a way that they can be perfectly reversed. As far as my theoretical understanding goes, doesn't reversing defeat the purpose of encryption in first place? If not, then I would like to encrypt them and then some mechanism to decrypt them. There are some solutions I found, one of them mentions retrieving but I cannot figure out which entity is which. I am pasting the code below. Kindly have a look :

Encryption and decryption :

byte[] userBytes = username.getBytes("UTF-8");
byte[] keyBytes = key.getBytes("UTF-8");
//XOR scramble
byte[] encrypted = new byte[userBytes.length];
for(int i = 0; i < userBytes.length; i++){
   encrypted[i] = (byte)(userBytes[i] ^ keyBytes[i % keyBytes.length]);
}

BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(encrypted);

// webappB, decode the parameter
BASE64Decoder decoder = new BASE64Decoder();
byte[] decoded =  decoder.decodeBuffer( encoded );
//XOR descramble
byte[] decrypted = new byte[decoded.length];
for(int i = 0; i < decoded.length; i++){
   decrypted[i] = (byte)(decoded[i] ^ keyBytes[i % keyBytes.length] );
}

Answer:

This class has two public methods, one for generating token and another for validating it. It is abridged from much larger and more complex code, so, some errors might be introduced. There are also some tests embedded, so you can play with it immediately. Any way, I hope it will be sufficient to get you on the right track.

package tokens;

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class TokenUtils {

    private static final String HMAC_ALGO = "HmacSHA256";
    private static final String TOKEN_SEPARATOR = ":";
    private static final long MAX_AGE = 1_000 * 60 * 60 * 24; // 24h

    private TokenUtils() {
    }

    public static String createToken(String username, long timestamp, String secretKey) throws InvalidKeyException, NoSuchAlgorithmException {
        StringBuilder sb = new StringBuilder();
        sb.append(generateTokenStringPublicPart(username, timestamp));
        sb.append(TOKEN_SEPARATOR);
        sb.append(computeSignature(username, timestamp, secretKey));
        return sb.toString();
    }

    public static boolean verifyToken(String token, String secretKey) throws InvalidKeyException, NoSuchAlgorithmException {
        String[] parts = token.split(TOKEN_SEPARATOR);
        boolean result = false;
        if (parts.length == 3) {
            String username = parts[0];
            Long timestamp = Long.valueOf(parts[1]);
            String signature = parts[2];
            if (signature.equals(computeSignature(username, timestamp, secretKey))) {
                if (System.currentTimeMillis() - timestamp < MAX_AGE) {
                    result = true;
                }
            }
        }
        return result;
    }

    private static String generateTokenStringPublicPart(String username, long timestamp) {
        StringBuilder sb = new StringBuilder();
        sb.append(username);
        sb.append(TOKEN_SEPARATOR);
        sb.append(timestamp);
        return sb.toString();
    }

    private static String computeSignature(String username, long timestamp, String secretKey) throws InvalidKeyException, NoSuchAlgorithmException {
        StringBuilder sb = new StringBuilder();
        sb.append(generateTokenStringPublicPart(username, timestamp));
        SecretKeySpec sks = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), HMAC_ALGO);
        Mac hmac = Mac.getInstance(HMAC_ALGO);
        hmac.init(sks);
        return Base64.getUrlEncoder().encodeToString(hmac.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8)));
    }

    public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException {
        String secretKey = "secret_key";
        String token = TokenUtils.createToken("marko", System.currentTimeMillis(), secretKey);
        System.out.println(token);
        System.out.println("Original token verification: " + TokenUtils.verifyToken(token, secretKey));
        token = token.replaceAll("a", "b");
        System.out.println("Tampered token verification: " + TokenUtils.verifyToken(token, secretKey));
        token = TokenUtils.createToken("marko", System.currentTimeMillis() - 1_000 * 60 * 60 * 48, secretKey);
        System.out.println("Expired token verification: " + TokenUtils.verifyToken(token, secretKey));
    }

}

Question:

Based on a property set at environment level

store.date.format=DD/MM/YY (or)
store.date.format=MM/DD/YY 

How can I bind String entered in the screen to joda LocalDate

@DateTimeFormat("dd/mm/yy")
private LocalDate birthDate;

I want birthDate to accept both dd/mm/yy and mm/dd/yy formats based on store setting.


Answer:

Use a property in the pattern attribute value.

@DateTimeFormat(pattern = "${store.date.format}")
private LocalDate birthDate;