Dmytro Nosan opened SPR-16614 and commented

Sometimes to localize some bean we have to use several sources. e.g.

MessageSource m1 = db.getTranslations()
MessageSource m2 = someService.getTranslations();

and then we will use a custom method for localizing

private String translate(MessageSource m1, MessageSource m2, String code, Locale locale) {
        try {
            return m1.getMessage(code, null, locale);
        } catch (NoSuchMessageException ex) {
            return m2.getMessage(code, null, locale);
        }
    }

But this method has several disadvantages: 1. If m1 has (useCodeAsDefaultMessage) we will never get a message from m2. 2. A lot of exceptions could be thrown 3. What would we do, if we need one or more MessageSource(s)?

My idea is to create a new ComposedMessageSource class which should handle: 1. Resolve message from both MessageSources 2. Resolve situation when one of the MessageSources has a property useCodeAsDefaultMessage (we should find the best option for localization) 3. Unnecessary exception generation

also, it would be great to have a utility class for composing MessageSources.

public abstract class MessageSources {
    @Nullable
    public static MessageSource compose(@Nullable MessageSource messageSource, @Nullable MessageSource parentMessageSource){
...
}

Affects: 5.0.4

Attachments: - ComposedMessageSource.java (4.30 kB) - ComposedMessageSourceTests.java (5.75 kB) - MessageSource.java (3.86 kB) - MessageSources.java (1.28 kB) - MessageSourcesTest.java (686 bytes)

Comment From: spring-projects-issues

Juergen Hoeller commented

We have a 'parent' arrangement for MessageSource providers which provides delegation to the parent for a message not found in the given source. Could you leverage that for your purposes, building a chain of MessageSource delegates?

Comment From: spring-projects-issues

Dmytro Nosan commented

Thanks for the answer.

In general, you can build a chain with:


public class MessageSourceComposite implements MessageSource {

    private final List<MessageSource> messageSources = new ArrayList<>();

    public void add(MessageSource... messageSources) {
        if (messageSources != null) {
            for (MessageSource messageSource : messageSources) {
                if (messageSource != null) {
                    this.messageSources.add(messageSource);
                }
            }
        }
    }

    @Override
    public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) {
        try {
            return getMessage(code, args, locale);
        } catch (NoSuchMessageException ex) {
            return defaultMessage;
        }
    }

    @Override
    public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException {
        for (MessageSource messageSource : messageSources) {
            try {
                return messageSource.getMessage(code, args, locale);
            } catch (NoSuchMessageException ignored) {
            }
        }
        throw new NoSuchMessageException(code, locale);
    }

    @Override
    public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException {
        for (MessageSource messageSource : messageSources) {
            try {
                return messageSource.getMessage(resolvable, locale);
            } catch (NoSuchMessageException ignored) {
            }
        }
        String[] codes = resolvable.getCodes();
        throw new NoSuchMessageException(!ObjectUtils.isEmpty(codes) ? codes[codes.length - 1] : null, locale);

    }
}

But you will still have an issue with useCodeAsDefaultMessage and exception generation. Could you please provide your own solution?

Thanks

Comment From: spring-projects-issues

Juergen Hoeller commented

I was referring to AbstractMessageSource.getMessageInternal/getMessageFromParent and its use of a parent hierarchy coming from setParentMessageSource (which can also be set via the HierarchicalMessageSource interface). Through internal delegation to getMessageInternal in case of an AbstractMessageSource parent, we also bypass the useCodeAsDefaultMessage problem and do not rely on an exception for the not-found case.

Since we have such a delegation model in the core framework already, we won't add another variant out of the box. If this is not sufficient for your purposes, you're free to build your own on top, of course.

Comment From: spring-projects-issues

Dmytro Nosan commented

Thanks for the explanation. It is really great, but it would be nice to have a support this feature from the box. I believe that it would help not only in this particular case, which I have described, it will simplify work with a conjunction of several message sources in general.


public class ComposedMessageSource extends AbstractMessageSource {

    @Nullable
    private MessageSource messageSource;

    @Nullable
    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setMessageSource(@Nullable MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    protected String getMessageInternal(String code, Object[] args, Locale locale) {
        String message;
        if (messageSource != null) {
            if (messageSource instanceof AbstractMessageSource) {
                message = ((AbstractMessageSource) messageSource).getMessageInternal(code, args, locale);
            } else {
                //custom messageSource
                message = messageSource.getMessage(code, args, null, locale);
            }
            if (message != null) {
                return message;
            }
        }

        message = getMessageFromParent(code, args, locale);
        if (message != null) {
            return message;
        }

        //should be applied after both messageSources.
        Properties commonMessages = getCommonMessages();
        if (commonMessages != null) {
            String commonMessage = commonMessages.getProperty(code);
            if (commonMessage != null) {
                return formatMessage(commonMessage, args, locale);
            }
        }

        return null;
    }

    @Override
    protected MessageFormat resolveCode(String code, Locale locale) {
        return null; //maybe UnsupportedOperation ? 
    }

}

Thanks.

Comment From: snicoll

I believe this is more natural to use a hierarchy rather than the composite. Besides there hasn't been a lot of traction for this request so I am going to close it. Thanks for the suggestion, in any case!