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());
}
}