/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.common.exceptions;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Logger;
import org.ebayopensource.turmeric.common.v1.types.CommonErrorData;
import org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider;
import org.ebayopensource.turmeric.runtime.common.errors.ErrorTextResolver;
import org.ebayopensource.turmeric.runtime.common.utils.Preconditions;
/**
* This is the property based Error Library implementation of the
* ErrorDataProvider.
*
* @author ana, wdeng
*
*/
public class PropertyFileBasedErrorProvider implements ErrorDataProvider{
private final static Logger LOG = Logger.getLogger(PropertyFileBasedErrorProvider.class.getName());
private final static String BUNDLE_NAME = "Errors";
private final static String ERRORDATACOLLECTION_CALSSNAME = "ErrorDataCollection";
private final static String ERRORCONSTANTS_CALSSNAME = "ErrorConstants";
private final static String ERRORDOMAINCONSTANTNAME = "ERRORDOMAIN";
private final static String MESSAGE_PROPERTY = ".message";
private final static String DEFAULT_CLASS_PACKAGE = "com.ebay.errorlibrary";
private final static String DEFAULT_RESOURCE_PACKAGE = "META-INF.errorlibrary";
private static Map<ErrorNameDomain, CommonErrorData> s_domainErrorDataMap = new HashMap<ErrorNameDomain, CommonErrorData>();
private static ConcurrentMap<String, String> s_domainPackageMap = new ConcurrentHashMap<String, String>();
private static List<Class> errorTypesClassList = new ArrayList<Class>();
private static Map<String,ErrorTextResolver> s_bundleResolvers =
new HashMap<String,ErrorTextResolver>();
private static PropertyFileBasedErrorProvider s_propertyFileBasedErrorProvider;
private PropertyFileBasedErrorProvider(){
}
/**
*
* @return The singleton PrapertyFileBasedErrorProvider.
*/
public static PropertyFileBasedErrorProvider getInstance(){
if(s_propertyFileBasedErrorProvider == null)
s_propertyFileBasedErrorProvider =new PropertyFileBasedErrorProvider();
return s_propertyFileBasedErrorProvider;
}
/**
* Initial setup for a domain. This method will load the ErrorData for the given
* domain.
*
* @param domain The error domain for which CommonErrorDatas are loaded.
*/
public synchronized void initialize(String domain) {
Class errorDataClass = getRequiredClass(getErrorDataClassPackage(domain), ERRORDATACOLLECTION_CALSSNAME);
try {
if(errorDataClass != null && !errorTypesClassList.contains(errorDataClass)){
errorTypesClassList.add(errorDataClass);
populateResourceBundleResolver(domain);
Field[] allFields = errorDataClass.getFields();
for (Field field : allFields) {
if (field != null && field.getType().equals(CommonErrorData.class)) {
CommonErrorData errorData = (CommonErrorData) field.get(null);
ErrorNameDomain errorNameDomain = new ErrorNameDomain(field.getName(), errorData.getDomain());
s_domainErrorDataMap.put(errorNameDomain, errorData);
}
}
validate(domain);
}
} catch (Exception exception) {
Object[] arguments = new Object[] { domain };
ExceptionUtils.throwServiceRuntimeException(ErrorLibraryBaseErrors.el_initialization_failed, arguments, exception);
}
}
private Class getRequiredClass(String packageName, String className){
Class requiredClass;
String requiredClassName = packageName + "." + className;
requiredClass = ExceptionUtils.loadClass(requiredClassName,null,
Thread.currentThread().getContextClassLoader());
return requiredClass;
}
private static String getErrorDataClassPackage(String domain){
// StringBuffer packageNameBuilder = new StringBuffer(100);
// packageNameBuilder.append(DEFAULT_CLASS_PACKAGE);
// if(domain != null){
// packageNameBuilder.append(".").append(domain.toLowerCase());
// }
String packageName = s_domainPackageMap.get(domain);
if(packageName == null){
InputStream inputStream = null;
URL errorDataXmlUrl = ExceptionUtils.getErrordataXMLURL(domain);
if(errorDataXmlUrl != null)
try {
inputStream = errorDataXmlUrl.openStream();
packageName = ExceptionUtils.getPackageNameFromXML(inputStream);
if(packageName != null)
s_domainPackageMap.putIfAbsent(domain, packageName.toLowerCase());
} catch (Exception exception) {
exception.printStackTrace();
}
}
return packageName;
}
private void validate(String domain){
Class errorConstantsClass = getRequiredClass(getErrorDataClassPackage(domain), ERRORCONSTANTS_CALSSNAME);
Set<String> constantsClassErrorSet = new HashSet<String>();
Set<String> propertiesErrorSet = new HashSet<String>();
String errorBundleName = getErrorBundleName(domain);
// Populating the set with the error names from ErrorConstants.java and Errors.properties.
// The key in the properties file is <ErrorName>.message and hence populating the set from
// ErrorConstants.java with <ErrorName>.message and check for consistencies. The constraint here
// is Errors.properties must contain all the errors defined in ErrorConstants.java
try {
if(errorConstantsClass != null){
Field[] allFields = errorConstantsClass.getFields();
for (Field field : allFields)
if(field != null && !field.getName().equals(ERRORDOMAINCONSTANTNAME)){
String propertyValue = (String)field.get(null);
if(propertyValue != null)
constantsClassErrorSet.add(propertyValue + MESSAGE_PROPERTY);
}
Locale locale2 = Locale.US;
ResourceBundle rBundle = ResourceBundle.getBundle(errorBundleName, locale2, Thread.currentThread().getContextClassLoader());
propertiesErrorSet = rBundle.keySet();
}
} catch (Exception exception) {
Object[] arguments = new Object[] {"ErrorData.xml" };
ExceptionUtils.throwServiceRuntimeException(ErrorLibraryBaseErrors.el_io_error, arguments, exception);
}
constantsClassErrorSet.removeAll(propertiesErrorSet);
if(!constantsClassErrorSet.isEmpty()){
StringBuilder logmsg = new StringBuilder();
logmsg.append("Not all found error properties found in bundle \"");
logmsg.append(errorBundleName);
logmsg.append("\" : Missing [");
boolean delim = false;
for(String constantsClassError: constantsClassErrorSet) {
if(delim) {
logmsg.append(", ");
}
logmsg.append(constantsClassError);
delim = true;
}
logmsg.append("]");
LOG.warning(logmsg.toString());
Object[] arguments = new Object[] {domain};
ExceptionUtils.throwServiceRuntimeException(ErrorLibraryBaseErrors.el_validation_failed, arguments);
}
}
private String getErrorBundleName(String domain){
return DEFAULT_RESOURCE_PACKAGE + "." + domain + "." + BUNDLE_NAME;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider#getCommonErrorData(org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider.ErrorDataKey, java.lang.Object[], java.util.Locale)
*/
@Override
public CommonErrorData getCommonErrorData(ErrorDataKey key, Object[] args,
Locale locale) {
ErrorNameDomain errorNameDomain = new ErrorNameDomain(key.getErrorName(), key.getBundle());
CommonErrorData tempErrorData = s_domainErrorDataMap.get(errorNameDomain);
if(tempErrorData == null){
initialize(key.getBundle());
tempErrorData = s_domainErrorDataMap.get(errorNameDomain);
}
if(tempErrorData == null){
Object[] arguments = new Object[] {key.getErrorName(), key.getBundle() };
ExceptionUtils.throwServiceRuntimeException(ErrorLibraryBaseErrors.el_no_such_error_defined, arguments);
}
CommonErrorData commonErrorData = ExceptionUtils.cloneErrorData(key, tempErrorData, args);
buildMessageAndResolution(commonErrorData, locale.getLanguage(), args);
return commonErrorData;
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider#getCustomErrorData(org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider.ErrorDataKey, java.lang.Object[], java.lang.Class, java.util.Locale)
*/
@SuppressWarnings("unchecked")
@Override
public <T extends CommonErrorData> T getCustomErrorData(ErrorDataKey key,
Object[] args, Class<T> clazz, Locale locale) {
return (T) getCommonErrorData(key, args, locale);
}
@Override
public CommonErrorData getErrorData(ErrorDataKey key, Object[] args, Locale locale) {
return getCommonErrorData(key, args, locale);
}
/* (non-Javadoc)
* @see org.ebayopensource.turmeric.runtime.common.errors.ErrorDataProvider#init()
*/
@Override
public void init() {
// no op
}
/**
* This method built the message and resolution for a given ErrorData. It include fetching
* the CommonErrorData from Error Libraries and localized the messages.
* @param errorData The CommonErrorData where the message and resolution are both
* constructed and localized.
* @param locale The local the message will be converted to
* @param params parameters to be used
*/
public void buildMessageAndResolution(CommonErrorData errorData, String locale, Object[] params) {
CommonErrorData commonErrorData = null;
String message = null;
String resolution = null;
ErrorTextResolver m_errorTextResolver = null;
if(errorData instanceof CommonErrorData){
commonErrorData = (CommonErrorData) errorData;
if(commonErrorData != null && commonErrorData.getDomain() != null){
m_errorTextResolver = s_bundleResolvers.get(commonErrorData.getDomain());
String messageId = commonErrorData.getErrorName() + ".message";
String resolutionId = commonErrorData.getErrorName() + ".resolution";
if(m_errorTextResolver == null)
populateResourceBundleResolver(commonErrorData.getDomain());
if(m_errorTextResolver != null){
// Get localized version here, to keep caching logic below working
if (locale != null) {
message = m_errorTextResolver.getErrorText(messageId, commonErrorData.getDomain(), locale);
resolution = m_errorTextResolver.getErrorText(resolutionId, commonErrorData.getDomain(), locale);
}
// If localized text not found, get the English version
if (message == null || message.isEmpty()) {
message = m_errorTextResolver.getErrorText(messageId, commonErrorData.getDomain(), null);
// If the English version is not found, it is an error.
if (message == null) {
message = "Unable to retrieve the message. Error message not defined in" +
" the bundle for Error " + commonErrorData.getDomain() + "." + commonErrorData.getErrorName();
commonErrorData.setMessage(message);
return;
}
}
if (resolution == null || resolution.isEmpty()) {
resolution = m_errorTextResolver.getErrorText(resolutionId, commonErrorData.getDomain(), null);
// Uncomment these once the resolution is ready.
// if (resolution == null || resolution.isEmpty()) {
// resolution = "Unable to retrieve the resolution message. Resolution might not be defined for Error " +
// commonErrorData.getDomain() + "." + commonErrorData.getErrorName();
// }
}
try {
message = MessageFormat.format(message, params);
} catch (Exception e) {
message = "Error Message Formatting error for Error ID " + commonErrorData.getDomain() + "." + commonErrorData.getErrorId() + ". Exception: " + e.toString();
}
}
}
commonErrorData.setMessage(message);
commonErrorData.setResolution(resolution);
}
}
private synchronized void populateResourceBundleResolver(String domain)
{
String bundlePackage = getErrorDataClassPackage(domain);
String errorBundleName = getErrorBundleName(domain);
ErrorTextResolver result = s_bundleResolvers.get(domain);
if (result == null) {
ClassLoader loader = null;
if (bundlePackage != null) {
String errorCollectionClass = bundlePackage + "." + ERRORDATACOLLECTION_CALSSNAME;
for(Class class1 : errorTypesClassList)
if(class1.getName().equals(errorCollectionClass)){
loader = class1.getClassLoader();
break;
}
if (loader == null) {
// we're in system class loader
loader = ClassLoader.getSystemClassLoader();
}
} else {
loader = Thread.currentThread().getContextClassLoader();
}
result = new ResourceBundleErrorTextResolver(errorBundleName, loader);
s_bundleResolvers.put(domain, result);
}
}
private static class ResourceBundleErrorTextResolver implements ErrorTextResolver {
private final String m_bundleName;
private final ClassLoader m_loader;
ResourceBundleErrorTextResolver(String bundleName, ClassLoader loader) {
Preconditions.checkNotNull(bundleName);
Preconditions.checkNotNull(loader);
m_bundleName = bundleName;
m_loader = loader;
}
public String getErrorText(String id, String domain, String locale) {
String result = null;
try {
Locale locale2 = (locale == null) ? Locale.US : new Locale(locale);
ResourceBundle rb = ResourceBundle.getBundle(m_bundleName, locale2, m_loader);
result = rb.getString(id);
} catch (MissingResourceException e) {
// ignore errors
} catch (ClassCastException e) {
// ignore errors
}
return result;
}
}
}