Custom formatting with Spring MVC annotation

I am migrating a Java EE application to spring boot and I am stuck with a conversion issue. Now, good or bad, I have kept my currencies as Long (its German Euro). I wrote a custom jsf converter that does something like this:

Long → String

22 → 00.22

3310 → 33.10

String → Long

3 → 3

22.11 → 2211

Now spring MVC has been one of the reasons for moving away from JSF. I would like to use 303 Beanvalidation with spring MVC (@Valid @ModelAttribute, BindingResult which works great for @Pattern for example)

Now I cannot use @NumberFormat (style = Style.Currency), which will do what I want if I haven't saved my currency yet.

I wrote a custom Formatter and registered it with the FormatterRegistry

public class LongCurrencyFormatter implements Formatter<Long>{

@Getter
private static final long serialVersionUID = 1L;

@Override
public String print(Long arg0, Locale arg1) {
  //logic removed for shorter post
}

@Override
public Long parse(String arg0, Locale arg1) throws ParseException {
    //logic removed for shorter post
}
}

      

everything is working up to this point, but now every long one is being converted. I think it's correct. So after some research I looked at 6.6.2 Formatting with annotation http://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html

I created, as in the documentation, AnnotationFormatterFactory

public class LongCurrencyFormatAnnotationFormatterFactory 
implements AnnotationFormatterFactory<LongCurrency> {

@Override
public Set<Class<?>> getFieldTypes() {

    Set<Class<?>> setTypes = new HashSet<Class<?>>();
    setTypes.add(Long.class);
    return setTypes;
}

@Override
public Parser<?> getParser(LongCurrency annotation, Class<?> fieldType) {
    return new LongCurrencyFormatter();
}

@Override
public Printer<?> getPrinter(LongCurrency annotation, Class<?> fieldType) {
    return new LongCurrencyFormatter();
}


}

      

My annotation:

public @interface LongCurrency {

}

      

My Bean:

public class Costunit {

  //other attributes

  @LongCurrency
  private long value; 
}

      

Unfortunately it does not work: Could not convert property value of type java.lang.String to required type long for property value; inested exception is java.lang.NumberFormatException: for input line: "22.00"

Sorry for the long post, any idea what I did wrong? Or any better solution to bind formatting with just one controller? Databasemirgration should be the smallest option.

Thank!

EDIT1: Complete Formatter code (works, but could be better of course)

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
import java.util.regex.Pattern;

import lombok.Getter;

import org.springframework.format.Formatter;

public class LongCurrencyFormatter implements Formatter<Long>{

@Getter
private static final long serialVersionUID = 1L;

@Override
public String print(Long arg0, Locale arg1) {

    String returnValue = arg0.toString();
    boolean minusChar =  returnValue.startsWith("-");
    returnValue = returnValue.replace("-", "");

    if (returnValue.length() > 2) {

        String tempStr = returnValue.substring(0, returnValue.length()-2);
        Long val = Long.parseLong(tempStr);

        DecimalFormat df = new DecimalFormat();
        df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.GERMAN)); 

        String output = df.format(val) + "," + 
        returnValue.substring(returnValue.length()-2);
        returnValue = output;

    } else {

        if(returnValue.length() == 1) {
            returnValue = "0,0"+returnValue;
        } else {
            returnValue = "0,"+returnValue;
        }

    }

    if(minusChar) {
        returnValue = "-" + returnValue;
    }

    return returnValue;
}

@Override
public Long parse(String arg0, Locale arg1) throws ParseException {

    Long returnLong = null;

    // 1Test :only one - in front, only digits and "." and one "," , and
    // only 2 digits behind ","
    // if "," only 2 not 1 digit behind
    if (!isValidateLongCurrency(arg0)) {

        returnLong = 0L;

    } else {

        String valueFiltered = arg0.replace(".", "");

        // 2: add 2 00 if no ",":
        if (!valueFiltered.contains(",")) {
            valueFiltered += "00";
        }
        else {

            //E,C or E,CC
            String[] splittedValue = valueFiltered.split(",");
            if(splittedValue[splittedValue.length-1].length() == 1) {
                valueFiltered = valueFiltered + 0; 
            }

            valueFiltered = valueFiltered.replace(",", "");
        }
        try {
            returnLong = new Long(valueFiltered);
        } catch (NumberFormatException numEx) {

        }
    }
    return returnLong;
}

private boolean isValidateLongCurrency(String value) {
    boolean returnValue = true;
    String valueFiltered = value.replace(".", "");

    //Euro
    String regEx = "^-?[1-9][0-9]*(,[0-9][0-9]?)?$|^-?[0-9](,[0-9][0-9]?)?$|^$";

    returnValue = Pattern.matches( regEx, valueFiltered ) ;


    return returnValue;
}
}

      

EDIT 2, now it works

Changes made:

import java.lang.annotation.*;

@Target(value={ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER})
@Retention(value=RetentionPolicy.RUNTIME)
public @interface LongCurrency {

}

@Override
public void addFormatters(FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addFormatterForFieldAnnotation(new 
    LongCurrencyFormatAnnotationFormatterFactory());
}

      

Thanks to M. Deinum

+1


source to share


1 answer


For starters, your annotation no longer exists. You have to make sure it persists at runtime, annotations are removed by default. To do this, add annotation @Retention

to the annotation. You will probably also want to add an annotation @Target

to indicate what types it can be set to.

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LongCurrency {}

      

Then make sure you register yours LongCurrencyFormatAnnotationFormatterFactory

correctly. If you do not register it, it will not be used.



@Override 
public void addFormatters(FormatterRegistry registry) {
    registry.addFormatterForFieldAnnotation(new LongCurrencyFormatAnnotationFormatterFactory()); 
}

      

Both changes should make your formatter get called / used.

+3


source







All Articles