Search code examples
jsfconvertersbigdecimal

Overriding the JSF converter javax.faces.convert.BigDecimalConverter in favor of a custom converter using @FacesConverter(forClass = BigDecimal.class)


I have the following general BigDecimal converter (deeply reviewing the code is absolutely superfluous).

@FacesConverter(value = "bigDecimalConverter")
public class BigDecimalConverter implements Converter {

    @Inject
    private CurrencyRateBean currencyRateBean; // Maintains a currency selected by a user in his/her session.

    private static final int scale = 2; // Taken from an enum.

    @Override
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if (!StringUtils.isNotBlank(value)) {
            return null;
        }

        try {
            BigDecimal bigDecimal = new BigDecimal(value);
            return bigDecimal.scale() > scale ? bigDecimal.setScale(scale, RoundingMode.HALF_UP).stripTrailingZeros() : bigDecimal.stripTrailingZeros();
        } catch (NumberFormatException e) {
            throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, null, "Message"), e);
        }
    }

    @Override
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        if (value == null) {
            return "";
        }

        BigDecimal newValue;
        if (value instanceof Long) {
            newValue = BigDecimal.valueOf((Long) value);
        } else if (value instanceof Double) {
            newValue = BigDecimal.valueOf((Double) value);
        } else if (!(value instanceof BigDecimal)) {
            throw new ConverterException("Message");
        } else {
            newValue = (BigDecimal) value;
        }

        final String variant = (String) component.getAttributes().get("variant");

        if (variant != null) {
            if (variant.equalsIgnoreCase("grouping")) {
                DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
                formatter.setGroupingUsed(true);
                formatter.setMinimumFractionDigits(scale);
                formatter.setMaximumFractionDigits(scale);
                return formatter.format(newValue);

            } else if (variant.equalsIgnoreCase("currency")) {
                String currency = currencyRateBean.getCurrency();
                DecimalFormat formatter = (DecimalFormat) NumberFormat.getCurrencyInstance(new Locale("en", new String(currency.substring(0, 2))));
                formatter.setDecimalFormatSymbols(formatter.getDecimalFormatSymbols());
                formatter.setCurrency(Currency.getInstance(currency));
                return formatter.format(newValue);
            }
        }

        DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance();
        formatter.setGroupingUsed(false); // Not necessary.
        formatter.setMinimumFractionDigits(scale);
        formatter.setMaximumFractionDigits(scale);
        return formatter.format(newValue);
    }
}

This can be used as follows.

<h:outputText value="#{bean.value}">
    <f:converter converterId="bigDecimalConverter"/>
    <f:attribute name="variant" value="grouping"/>
</h:outputText>

Depending upon the value of variant in <f:attribute>, it converts the value to either currency equivalent ($123) or a value using groups (11,111). Other criteria may also be defined as and when required i.e separate converters for other types of formats, if any, are not required. The default task of the converter is to round up the value to two decimal points.

In order to avoid mentioning of,

<f:converter converterId="bigDecimalConverter"/>

or converter="#{bigDecimalConverter}" everywhere, the converter class needs to be decorated with,

@FacesConverter(forClass = BigDecimal.class)

Doing so, JSF takes its own javax.faces.convert.BigDecimalConverter beforehand. I have tried extending that class but it did not make any difference.

Is it possible to override the default JSF javax.faces.convert.BigDecimalConverter so that our own converter can be used by specifying forClass = BigDecimal.class so that there is no need to fiddle around with <f:converter converterId="bigDecimalConverter"/> or converter="#{bigDecimalConverter}" anywhere?


Solution

  • In general, overriding default converters (and validators, components and renderers) can't take place with annotations on the same identifier(s). It really has to be explicitly registered in webapp's own faces-config.xml.

    In case you intend to override converter="javax.faces.BigDecimal", then do so:

    <converter>
        <converter-id>javax.faces.BigDecimal</converter-id>
        <converter-class>com.example.YourBigDecimalConverter</converter-class>
    </converter>
    

    In case you intend to override implicit conversion for type java.math.BigDecimal, then do so:

    <converter>
        <converter-for-class>java.math.BigDecimal</converter-for-class>
        <converter-class>com.example.YourBigDecimalConverter</converter-class>
    </converter>
    

    You need the latter.