package com.linkedin.databus.core.util; /* * * Copyright 2013 LinkedIn Corp. 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 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ import java.beans.PropertyDescriptor; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.beanutils.BeanUtilsBean; import org.apache.commons.beanutils.PropertyUtilsBean; import org.apache.log4j.Logger; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.type.TypeReference; /** * A helper class for loading configurations from property files and JSON * @author cbotev * * @param <D> Config class */ public class ConfigLoader<D> extends ReadWriteSyncedObject { public final static String MODULE = ConfigLoader.class.getName(); public final static Logger LOG = Logger.getLogger(MODULE); protected final String _propPrefix; protected final ConfigBuilder<D> _configBuilder; private BeanUtilsBean _beanUtilsBean; public ConfigLoader(String propPrefix, ConfigBuilder<D> dynConfigBuilder) throws InvalidConfigException { super(true); _propPrefix = propPrefix; _configBuilder = dynConfigBuilder; _beanUtilsBean = new BeanUtilsBean(); } public D loadConfig(Map<?, ?> props) throws InvalidConfigException { Map<String, Object> realProps = new HashMap<String, Object>(); D result = null; if (_propPrefix != null && _propPrefix.length() > 0) { int prefixLen = _propPrefix.length(); for (Map.Entry<?, ?> entry: props.entrySet()) { String keyStr = entry.getKey().toString(); if (keyStr.startsWith(_propPrefix)) { String realKey = keyStr.substring(prefixLen); realProps.put(realKey, entry.getValue()); } } } else { for (Map.Entry<?, ?> entry: props.entrySet()) { realProps.put(entry.getKey().toString(), entry.getValue()); } } synchronized (_configBuilder) { for (Map.Entry<String, Object> entry : realProps.entrySet()) { setBareSetting(entry.getKey(), entry.getValue()); } result = _configBuilder.build(); } return result; } private void setBareSetting(String settingName, Object value) throws InvalidConfigException { //check syntax because beanutils silently ignore settings with typos PropertyDescriptor propDesc = null; try { propDesc = _beanUtilsBean.getPropertyUtils().getPropertyDescriptor(_configBuilder, settingName); } catch (Exception e) { throw new InvalidConfigException("Error obtaining configuration property. settingName=" + settingName, e); } if (null == propDesc) { throw new InvalidConfigException("Unknown configuration property:" + settingName); } try { if (value instanceof String) { value = ((String)value).trim(); } _beanUtilsBean.setProperty(_configBuilder, settingName, value); } catch (Exception e) { throw new InvalidConfigException("setting failed: " + settingName, e); } } public D setSetting(String settingName, Object value) throws InvalidConfigException { synchronized (_configBuilder) { if (settingName.startsWith(_propPrefix)) { String realKey = settingName.substring(_propPrefix.length()); setBareSetting(realKey, value); } D newConfig = _configBuilder.build(); return newConfig; } } public D loadConfigFromJson(Reader jsonReader) throws InvalidConfigException { D newConfig = null; try { ObjectMapper _jsonMapper = new ObjectMapper(); Map<?, ?> jsonMap = _jsonMapper.readValue(jsonReader, new TypeReference<Map<?, ?>>(){}); synchronized (_configBuilder) { fillBeanFromMap(_configBuilder, jsonMap); } newConfig = _configBuilder.build(); } catch (Exception e) { throw new InvalidConfigException("Load from JSON failed", e); } return newConfig; } private void fillBeanFromMap(Object bean, Map<?, ?> map) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { PropertyUtilsBean propUtils = _beanUtilsBean.getPropertyUtils(); for (Map.Entry<?, ?> entry : map.entrySet()) { String keyStr = entry.getKey().toString(); Object value = entry.getValue(); if (value instanceof Map<?, ?>) { Object subBean = propUtils.getProperty(bean, keyStr); if (null != subBean) fillBeanFromMap(subBean, (Map<?, ?>)value); } else if (value instanceof List<?>) { fillPropertyFromList(bean, keyStr, (List<?>)value); } else { propUtils.setProperty(bean, keyStr, value); } } } private void fillPropertyFromList(Object bean, String propName, List<?> values) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { PropertyUtilsBean propUtils = _beanUtilsBean.getPropertyUtils(); int idx = 0; for (Object elem: values) { if (elem instanceof Map<?,?>) { Object subBean = propUtils.getIndexedProperty(bean, propName, idx); fillBeanFromMap(subBean, (Map<?, ?>)elem); } else { propUtils.setIndexedProperty(bean, propName, idx, elem); } ++idx; } } /** * Provides direct access to the config builder. */ public ConfigBuilder<D> getConfigBuilder() { return _configBuilder; } public String getPropPrefix() { return _propPrefix; } }