package org.marketcetera.util.log; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.text.MessageFormat; import java.util.Locale; import org.apache.commons.i18n.MessageManager; import org.apache.commons.i18n.MessageNotFoundException; import org.apache.commons.i18n.ResourceBundleMessageProvider; import org.apache.commons.lang.ObjectUtils; import org.marketcetera.util.except.ExceptUtils; import org.marketcetera.util.misc.ClassVersion; /** * An internationalized message provider, mapping instances of {@link * I18NMessage} onto text. The locale used for the translation is * either one supplied at the time of translation, or the active * locale per {@link ActiveLocale}; if the chosen locale does not * provide a message, the default JVM locale and root locale (in that * order) are used as fallbacks. * * <p>Message providers can be serialized. However, upon * deserialization, they are not guaranteed to have the same * classloader as during serialization, and hence may be unable to * access the same message files as were available during * serialization. If this happens, deserialization will fail.</p> * * @author tlerios@marketcetera.com * @since 0.5.0 * @version $Id: I18NMessageProvider.java 16154 2012-07-14 16:34:05Z colin $ */ /* $License$ */ @ClassVersion("$Id: I18NMessageProvider.java 16154 2012-07-14 16:34:05Z colin $") public class I18NMessageProvider implements Serializable { // CLASS DATA. private static final long serialVersionUID=1L; /** * The string added to the end of the provider ID to obtain the * stream resource (file) containing the message mappings. */ public static final String MESSAGE_FILE_EXTENSION= "_messages"; //$NON-NLS-1$ // "_message"; // EXTREME TEST 1. /* * Hard-coded message text for messages used when the mapping file * cannot be used. */ private static final String MESSAGE_FILE_NOT_FOUND= "Message file missing: provider '{}'; base name '{}'"; //$NON-NLS-1$ private static final String MESSAGE_NOT_FOUND= "Message missing: provider ''{0}''; id ''{1}''; "+ //$NON-NLS-1$ "entry ''{2}''; parameters {3}"; //$NON-NLS-1$ private static final String UNEXPECTED_EXCEPTION_CONTEXT= "Abnormal exception: provider ''{0}''; id ''{1}''; "+ //$NON-NLS-1$ "entry ''{2}''; parameters {3}"; //$NON-NLS-1$ private static final String UNEXPECTED_EXCEPTION_TRACE= "Abnormal exception: stack trace"; //$NON-NLS-1$ private static final String CORRUPTED_STORE= "Corrupted/unavailable message map"; //$NON-NLS-1$ // INSTANCE DATA. private String mProviderId; // CONSTRUCTORS. /** * Creates a new message provider with the given ID. The provider * ID is combined with the suffix {@link #MESSAGE_FILE_EXTENSION} * to form the name of a mapping file. The file should be * retrievable as a resource via the given class loader, and its * format should be that used by Apache Commons i18n. * * @param providerId The provider ID. * @param classLoader The class loader used to load the mapping * file. It may be null to use the default classloader. */ public I18NMessageProvider (String providerId, ClassLoader classLoader) { mProviderId=providerId; try { init(classLoader); } catch (MessageNotFoundException ex) { SLF4JLoggerProxy.error (this,MESSAGE_FILE_NOT_FOUND,getProviderId(),getBaseName()); SLF4JLoggerProxy.error(this,UNEXPECTED_EXCEPTION_TRACE,ex); } } /** * Creates a new message provider with the given ID. The provider * ID is combined with the suffix {@link #MESSAGE_FILE_EXTENSION} * to form the name of a mapping file. The file should be * retrievable as a resource via the default class loader, and its * format should be that used by Apache Commons i18n. * * @param providerId The provider ID. */ public I18NMessageProvider (String providerId) { this(providerId,null); } // INSTANCE METHODS. /** * Initializes the receiver. * * @param classLoader The class loader used to load the mapping * file. It may be null to use the default classloader. * * @throws MessageNotFoundException Thrown if initialization * fails. */ private void init (ClassLoader classLoader) throws MessageNotFoundException { ResourceBundleMessageProvider provider; if (classLoader==null) { provider=new ResourceBundleMessageProvider(getBaseName()); } else { provider=new ResourceBundleMessageProvider (getBaseName(),classLoader); } MessageManager.addMessageProvider(getProviderId(),provider); } /** * Returns the receiver's provider ID. * * @return The ID. */ public String getProviderId() { return mProviderId; } /** * Returns the receiver's base name. * * @return The base name. */ private String getBaseName() { return getProviderId()+MESSAGE_FILE_EXTENSION; } /** * Java deserialization. Reads a receiver instance from the given * stream. * * @param in The stream. * * @throws IOException Per serialization spec. * @throws ClassNotFoundException Per serialization spec. */ private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); try { init(null); } catch (MessageNotFoundException ex) { throw new IOException (Messages.MESSAGE_FILE_NOT_FOUND.getText (getProviderId(),getBaseName()),ex); } } /** * Returns the text of the given message in the given locale, * using the receiver's map. The given parameters are used to * instantiate parameterized messages. * * @param locale The locale. * @param message The message. * @param params The message parameters. * * @return Returns the text. If the message cannot be found, an * error is logged, and a simple form of the message comprising * its IDs and parameters is returned. */ public String getText (Locale locale, I18NMessage message, Object... params) { String messageId=message.getMessageId(); String entryId=message.getEntryId(); try { //throw new IllegalArgumentException(); // EXTREME TEST 2. return MessageManager.getText (getProviderId(),messageId,entryId,params,locale); } catch (Exception ex) { ExceptUtils.interrupt(ex); // Handle mutually recursive call. if ((message==Messages.MESSAGE_NOT_FOUND) || (message==Messages.UNEXPECTED_EXCEPTION)) { SLF4JLoggerProxy.error(this,CORRUPTED_STORE); if (message==Messages.MESSAGE_NOT_FOUND) { return MessageFormat.format(MESSAGE_NOT_FOUND,params); } SLF4JLoggerProxy.error (this,UNEXPECTED_EXCEPTION_TRACE,ex); return MessageFormat.format (UNEXPECTED_EXCEPTION_CONTEXT,params); } // Turn arguments into a string. String paramsText=LogUtils.getListText(params); // Show exception: this may result in a mutually recursive // call into this exception handler via Messages.LOGGER if, // for example, the core message map is missing. if (ex instanceof MessageNotFoundException) { Messages.MESSAGE_NOT_FOUND.error (this,ex,getProviderId(),messageId,entryId,paramsText); } else { Messages.UNEXPECTED_EXCEPTION.error (this,ex,getProviderId(),messageId,entryId,paramsText); SLF4JLoggerProxy.error(this,UNEXPECTED_EXCEPTION_TRACE,ex); } // Return simple form of message. return LogUtils.getSimpleMessage(this,message,params); } } /** * Returns the text of the given message in the active locale per * {@link ActiveLocale}. The given parameters are used to * instantiate parameterized messages. * * @param message The message. * @param params The message parameters. * * @return Returns the text. If the message cannot be found, an * error is logged, and a simple form of the message comprising * its IDs and parameters is returned. */ public String getText (I18NMessage message, Object... params) { return getText(ActiveLocale.getLocale(),message,params); } // Object. @Override public int hashCode() { return ObjectUtils.hashCode(getProviderId()); } @Override public boolean equals (Object other) { if (this==other) { return true; } if ((other==null) || !getClass().equals(other.getClass())) { return false; } I18NMessageProvider o=(I18NMessageProvider)other; return ObjectUtils.equals(getProviderId(),o.getProviderId()); } }