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
source to share
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.
source to share