Friday 4 November 2011

Aggregating MessageSource

I ran into this requirement in my recent task.

I need to overlay a war project onto another war project. Both war projects have their own messageSource bean defined. One uses the common ResourceBundleMessageSource. The other one uses a customized MessageSource, called RichTextMessageSource.

The idea is when a message code is required, search that message code in each of the MessageSources. If the message content is found, return that message. If no message content can be found through either of the MessageSources, throw a NoSuchMessageException.

The challenge lies in the identical bean name --- messageSource. If the bean name is changed, Spring is unable to load the MessageSource bean.

After doing online research, I decide to use an AggregatingMessageSource. (Enlightened by http://sacrephill.wordpress.com/2010/03/26/spring-aggregating-messagesource/)

Here is the source code of AggregatingMessageSource.java

/**
 *  MessageSource implementation that delegates to any other 
 *  MessageSource beans. 
 *  e.g. if you have two MessageSources, 'messageSource1' and 'messageSource2', 
 *  if a message 'customer.firstname' is requested, it will:
 *
 *  1. If the message is found through 'messageSource1', return that message.
 *  2. If the message is found through 'messageSource2', return that message.
 *  3. Else, throw a NoSuchMessageException.
 *
 * @author Mingtao Sun
 */
public class AggregatingMessageSource implements MessageSource {

    /** A list of MessageSources */
    private List messageSources = new ArrayList(1);

    /** @inheritDoc */
    public String getMessage(MessageSourceResolvable resolvable, 
                Locale locale)
            throws NoSuchMessageException {
        for (int i=0; i<messageSources.size(); i++){
            MessageSource messageSource = 
                (MessageSource)messageSources.get(i);
            try{
                String result = 
                messageSource.getMessage(resolvable, locale);    
                if (result != null){
                    return result;
                }    
            }catch(NoSuchMessageException e){
                //No message can be found in this messageSource
                //Move on to the next messageSource
            }
        }
        String[] codes = resolvable.getCodes();
        throw new NoSuchMessageException(codes.length > 0 
            ? codes[codes.length - 1] : null, locale);
    }

    /** @inheritDoc */
    public String getMessage(String code, Object[] args, Locale locale)
            throws NoSuchMessageException {
        String result = getMessage(code, args, null, locale);
        if (result == null) {
                    throw new NoSuchMessageException(code, locale);
            }
            return result;
    }

    /** @inheritDoc */
    public String getMessage(String code, Object[] args, 
                String defaultMessage, Locale locale) {
        for (int i=0; i<messageSources.size(); i++){
            MessageSource messageSource = (MessageSource)
                messageSources.get(i);
            try{
                String result = messageSource.getMessage
                    (code, args, defaultMessage, locale);    
                if (result != null){
                    return result;
                }    
            }catch(NoSuchMessageException e){
                //No message can be found in this messageSource
                //Move on to the next messageSource
            }
        }
        return null;
    }

    /**
     * Sets a list of MessageSources
     * @param messageSources a list of MessageSources to set
     */
    public void setMessageSources(List messageSources) {
        this.messageSources = messageSources;
    }
}

In the spring application context file, first thing to do is rename the ResourceBundleMessageSource and RichTextMessageSource.

<bean id="resourceMessageSource" 
      class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>messages</value>
            ……
        </list>
    </property>
</bean>

<bean id="richMessageSource" 
      class="com.smt.message.RichTextMessageSource">
    ……    
</bean>

Then define the messageSource bean.

<bean name="messageSource" 
      class="com.smt.message.AggregatingMessageSource">
    <property name="messageSources">
        <list>
            <ref bean="olcMessageSource"/>
            <ref bean="cicMessageSource"/>
        </list>
    </property>
</bean>