How to extend FormatStrings properly

I tried to write some more data types on Java time API than Jmix core provides. I also thought about suggesting them to the platform team, but tried to implement them ourselves first.

I wrote a class YearMonthDatatype extending AbstractTemporalDatatype<YearMonth>. The Jmix core FormatStrings are limited to a hard coded set of message bundle keys. So I try to load a custom pattern autowiring io.jmix.core.Messages in my datatype implementation. But testing that leads to a unresolvable circular reference:

Error creating bean with name 'core_MessageTools':
Unsatisfied dependency expressed through field 'metadata';
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'core_Metadata':
Requested bean is currently in creation: Is there an unresolvable circular reference?

I assume, I cannot autowire messages in datatype implementations - which is just a workaround anyway.

Is there a way to extend io.jmix.core.metamodel.datatype.FormatStrings in a clean and proper way?

Writing a datatype implementation for java.time.MonthDay is even a bit harder because I cannot extend io.jmix.core.metamodel.datatype.impl.AbstractTemporalDatatype.

Is that for a reason, it supports java.time.Temporal but does not java.time.TemporalAccessor ?

The FormatStrings class is not designed for extension. So using Messages in your datatype implementation is a better solution. It’s true that you cannot inject beans into it, because datatypes are initialized too early in the startup process, but you can inject ApplicationContext and then use its getBean() methods to locate any beans.

Thanks for the hint, it looks like we can safely use TemporalAccessor. Created issue:

Regards,
Konstantin

1 Like

@krivopustov Thank you for your answer.

I followed your suggestion and added a lazy lookup of the Messages bean.

@DatatypeDef(id = "yearMonth", javaClass = YearMonth.class, defaultForClass = true)
@Ddl("varchar(7)")
public class YearMonthDatatype extends AbstractTemporalDatatype<YearMonth>
{
    /**
     * Defines the message resource bundle key name holding the localized format pattern.
     */
    public static final String PATTERN_MESSAGE_KEY = "yearMonthFormat";

    /**
     * Formatter for {@link YearMonth} instances applying {@code ISO 8601}.
     */
    public static final DateTimeFormatter ISO_YEAR_MONTH;

    static
    {
        // copied from java.time.YearMonth (private)
        ISO_YEAR_MONTH = new DateTimeFormatterBuilder()
                        .appendValue(ChronoField.YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
                        .appendLiteral('-')
                        .appendValue(ChronoField.MONTH_OF_YEAR, 2)
                        .toFormatter();
    }

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * Holds the lazily instantiated messages bean. It cannot be injected directly due to the lookup order.
     */
    private Messages messages;

    /**
     * Public default constructor.
     */
    public YearMonthDatatype()
    {
        super(ISO_YEAR_MONTH);
    }

//...

    @Override
    protected DateTimeFormatter getDateTimeFormatter(final FormatStrings someFormatStrings, final Locale aLocale)
    {
        // not covered by the hard coded FormatStrings implementation
        final String pattern = obtainMessages().getMessage(PATTERN_MESSAGE_KEY, aLocale);

        return StringUtils.isBlank(pattern) ? null : DateTimeFormatter.ofPattern(pattern.trim(), aLocale);
    }
//...
/**
     * Obtains the messages instance using the {@link ApplicationContext#getBean(String)}.
     * 
     * @return messages
     */
    protected synchronized Messages obtainMessages()
    {
        if (messages == null)
        {
            messages = (Messages) applicationContext.getBean("core_Messages");
        }

        return messages;
    }
}