| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.apache.dubbo.config; |
| |
| import org.apache.dubbo.common.URL; |
| import org.apache.dubbo.common.config.ConfigurationUtils; |
| import org.apache.dubbo.common.config.Environment; |
| import org.apache.dubbo.common.config.InmemoryConfiguration; |
| import org.apache.dubbo.common.constants.CommonConstants; |
| import org.apache.dubbo.common.extension.ExtensionLoader; |
| import org.apache.dubbo.common.logger.Logger; |
| import org.apache.dubbo.common.logger.LoggerFactory; |
| import org.apache.dubbo.common.utils.ClassUtils; |
| import org.apache.dubbo.common.utils.CollectionUtils; |
| import org.apache.dubbo.common.utils.FieldUtils; |
| import org.apache.dubbo.common.utils.MethodUtils; |
| import org.apache.dubbo.common.utils.ReflectUtils; |
| import org.apache.dubbo.common.utils.StringUtils; |
| import org.apache.dubbo.config.context.ConfigManager; |
| import org.apache.dubbo.config.context.ConfigMode; |
| import org.apache.dubbo.config.support.Nested; |
| import org.apache.dubbo.config.support.Parameter; |
| import org.apache.dubbo.rpc.model.ApplicationModel; |
| import org.apache.dubbo.rpc.model.ModuleModel; |
| import org.apache.dubbo.rpc.model.ScopeModel; |
| import org.apache.dubbo.rpc.model.ScopeModelUtil; |
| |
| import java.beans.BeanInfo; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.MethodDescriptor; |
| import java.beans.PropertyDescriptor; |
| import java.io.Serializable; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import static org.apache.dubbo.common.utils.ClassUtils.isSimpleType; |
| import static org.apache.dubbo.common.utils.ReflectUtils.findMethodByMethodSignature; |
| import static org.apache.dubbo.config.Constants.PARAMETERS; |
| |
| /** |
| * Utility methods and public methods for parsing configuration |
| * |
| * @export |
| */ |
| public abstract class AbstractConfig implements Serializable { |
| |
| protected static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class); |
| private static final long serialVersionUID = 4267533505537413570L; |
| |
| /** |
| * tag name cache, speed up get tag name frequently |
| */ |
| private static final Map<Class, String> tagNameCache = new ConcurrentHashMap<>(); |
| |
| /** |
| * attributed getter method cache for equals(), hashCode() and toString() |
| */ |
| private static final Map<Class, List<Method>> attributedMethodCache = new ConcurrentHashMap<>(); |
| |
| /** |
| * The suffix container |
| */ |
| private static final String[] SUFFIXES = new String[]{"Config", "Bean", "ConfigBase"}; |
| |
| /** |
| * The config id |
| */ |
| private String id; |
| |
| protected final AtomicBoolean refreshed = new AtomicBoolean(false); |
| |
| /** |
| * Is default config or not |
| */ |
| protected Boolean isDefault; |
| |
| /** |
| * The scope model of this config instance. |
| * <p> |
| * <b>NOTE:</b> the model maybe changed during config processing, |
| * the extension spi instance needs to be reinitialized after changing the model! |
| */ |
| protected ScopeModel scopeModel; |
| |
| public AbstractConfig() { |
| this(ApplicationModel.defaultModel()); |
| } |
| |
| public AbstractConfig(ScopeModel scopeModel) { |
| this.setScopeModel(scopeModel); |
| } |
| |
| public static String getTagName(Class<?> cls) { |
| return tagNameCache.computeIfAbsent(cls, (key) -> { |
| String tag = cls.getSimpleName(); |
| for (String suffix : SUFFIXES) { |
| if (tag.endsWith(suffix)) { |
| tag = tag.substring(0, tag.length() - suffix.length()); |
| break; |
| } |
| } |
| return StringUtils.camelToSplitName(tag, "-"); |
| }); |
| } |
| |
| public static String getPluralTagName(Class<?> cls) { |
| String tagName = getTagName(cls); |
| if (tagName.endsWith("y")) { |
| // e.g. registry -> registries |
| return tagName.substring(0, tagName.length() - 1) + "ies"; |
| } else if (tagName.endsWith("s")) { |
| // e.g. metrics -> metricses |
| return tagName + "es"; |
| } |
| return tagName + "s"; |
| } |
| |
| public static void appendParameters(Map<String, String> parameters, Object config) { |
| appendParameters(parameters, config, null); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public static void appendParameters(Map<String, String> parameters, Object config, String prefix) { |
| appendParameters0(parameters, config, prefix, true); |
| } |
| |
| /** |
| * Put attributes of specify 'config' into 'parameters' argument |
| * |
| * @param parameters |
| * @param config |
| */ |
| public static void appendAttributes(Map<String, String> parameters, Object config) { |
| appendParameters0(parameters, config, null, false); |
| } |
| |
| public static void appendAttributes(Map<String, String> parameters, Object config, String prefix) { |
| appendParameters0(parameters, config, prefix, false); |
| } |
| |
| private static void appendParameters0(Map<String, String> parameters, Object config, String prefix, boolean asParameters) { |
| if (config == null) { |
| return; |
| } |
| // If asParameters=false, it means append attributes, ignore @Parameter annotation's attributes except 'append' and 'attribute' |
| |
| // How to select the appropriate one from multiple getter methods of the property? |
| // e.g. Using String getGeneric() or Boolean isGeneric()? Judge by field type ? |
| // Currently, use @Parameter.attribute() to determine whether it is an attribute. |
| |
| BeanInfo beanInfo = getBeanInfo(config.getClass()); |
| for (MethodDescriptor methodDescriptor : beanInfo.getMethodDescriptors()) { |
| Method method = methodDescriptor.getMethod(); |
| try { |
| String name = method.getName(); |
| if (MethodUtils.isGetter(method)) { |
| if (method.getReturnType() == Object.class) { |
| continue; |
| } |
| String key; |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (asParameters) { |
| if (parameter != null && parameter.excluded()) { |
| continue; |
| } |
| // get parameter key |
| if (parameter != null && parameter.key().length() > 0) { |
| key = parameter.key(); |
| } else { |
| key = calculatePropertyFromGetter(name); |
| } |
| } else { // as attributes |
| // filter non attribute |
| if (parameter != null && !parameter.attribute()) { |
| continue; |
| } |
| // get attribute name |
| String propertyName = calculateAttributeFromGetter(name); |
| // convert camelCase/snake_case to kebab-case |
| key = StringUtils.convertToSplitName(propertyName, "-"); |
| } |
| Object value = method.invoke(config); |
| String str = String.valueOf(value).trim(); |
| if (value != null && str.length() > 0) { |
| if (asParameters && parameter != null && parameter.escaped()) { |
| str = URL.encode(str); |
| } |
| if (parameter != null && parameter.append()) { |
| String pre = parameters.get(key); |
| if (pre != null && pre.length() > 0) { |
| str = pre + "," + str; |
| //Remove duplicate values |
| Set<String> set = StringUtils.splitToSet(str, ','); |
| str = StringUtils.join(set, ","); |
| } |
| } |
| if (prefix != null && prefix.length() > 0) { |
| key = prefix + "." + key; |
| } |
| parameters.put(key, str); |
| } else if (asParameters && parameter != null && parameter.required()) { |
| throw new IllegalStateException(config.getClass().getSimpleName() + "." + key + " == null"); |
| } |
| } else if (isParametersGetter(method)) { |
| Map<String, String> map = (Map<String, String>) method.invoke(config); |
| map = convert(map, prefix); |
| if (asParameters) { |
| // put all parameters to url |
| parameters.putAll(map); |
| } else { |
| // encode parameters to string for config overriding, see AbstractConfig#refresh() |
| String key = calculatePropertyFromGetter(name); |
| String encodeParameters = StringUtils.encodeParameters(map); |
| if (encodeParameters != null) { |
| parameters.put(key, encodeParameters); |
| } |
| } |
| } else if (isNestedGetter(config, method)) { |
| Object inner = method.invoke(config); |
| String fieldName = MethodUtils.extractFieldName(method); |
| String nestedPrefix = prefix == null ? fieldName : prefix + "." + fieldName; |
| appendParameters0(parameters, inner, nestedPrefix, asParameters); |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException("Append parameters failed: " + e.getMessage(), e); |
| } |
| } |
| } |
| |
| protected static String extractPropertyName(String setter) { |
| String propertyName = setter.substring("set".length()); |
| propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); |
| return propertyName; |
| } |
| |
| private static String calculatePropertyToGetter(String name) { |
| return "get" + name.substring(0, 1).toUpperCase() + name.substring(1); |
| } |
| |
| private static String calculatePropertyToSetter(String name) { |
| return "set" + name.substring(0, 1).toUpperCase() + name.substring(1); |
| } |
| |
| private static String calculatePropertyFromGetter(String name) { |
| int i = name.startsWith("get") ? 3 : 2; |
| return StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), "."); |
| } |
| |
| private static String calculateAttributeFromGetter(String getter) { |
| int i = getter.startsWith("get") ? 3 : 2; |
| return getter.substring(i, i + 1).toLowerCase() + getter.substring(i + 1); |
| } |
| |
| private static void invokeSetParameters(Class c, Object o, Map map) { |
| try { |
| Method method = findMethodByMethodSignature(c, "setParameters", new String[]{Map.class.getName()}); |
| if (method != null && isParametersSetter(method)) { |
| method.invoke(o, map); |
| } |
| } catch (Throwable t) { |
| // ignore |
| } |
| } |
| |
| private static Map<String, String> invokeGetParameters(Class c, Object o) { |
| try { |
| |
| Method method = findMethodByMethodSignature(c, "getParameters", null); |
| if (method != null && isParametersGetter(method)) { |
| return (Map<String, String>) method.invoke(o); |
| } |
| } catch (Throwable t) { |
| // ignore |
| } |
| return null; |
| } |
| |
| private static boolean isParametersGetter(Method method) { |
| String name = method.getName(); |
| return ("getParameters".equals(name) |
| && Modifier.isPublic(method.getModifiers()) |
| && method.getParameterTypes().length == 0 |
| && method.getReturnType() == Map.class); |
| } |
| |
| private static boolean isParametersSetter(Method method) { |
| return ("setParameters".equals(method.getName()) |
| && Modifier.isPublic(method.getModifiers()) |
| && method.getParameterCount() == 1 |
| && Map.class == method.getParameterTypes()[0] |
| && method.getReturnType() == void.class); |
| } |
| |
| private static boolean isNestedGetter(Object obj, Method method) { |
| String name = method.getName(); |
| boolean isGetter = (name.startsWith("get") || name.startsWith("is")) |
| && !"get".equals(name) && !"is".equals(name) |
| && !"getClass".equals(name) && !"getObject".equals(name) |
| && Modifier.isPublic(method.getModifiers()) |
| && method.getParameterTypes().length == 0 |
| && (!method.getReturnType().isPrimitive() && !isSimpleType(method.getReturnType())); |
| |
| if (!isGetter) { |
| return false; |
| } else { |
| // Extract fieldName only when necessary. |
| String fieldName = MethodUtils.extractFieldName(method); |
| Field field = FieldUtils.getDeclaredField(obj.getClass(), fieldName); |
| return field != null && field.isAnnotationPresent(Nested.class); |
| } |
| } |
| |
| private static boolean isNestedSetter(Object obj, Method method) { |
| boolean isSetter = method.getName().startsWith("set") |
| && !"set".equals(method.getName()) |
| && Modifier.isPublic(method.getModifiers()) |
| && method.getParameterCount() == 1 |
| && method.getParameterTypes()[0] != null |
| && (!method.getParameterTypes()[0].isPrimitive() && !isSimpleType(method.getParameterTypes()[0])); |
| |
| if (!isSetter) { |
| return false; |
| } else { |
| // Extract fieldName only when necessary. |
| String fieldName = MethodUtils.extractFieldName(method); |
| Field field = FieldUtils.getDeclaredField(obj.getClass(), fieldName); |
| return field != null && field.isAnnotationPresent(Nested.class); |
| } |
| } |
| |
| /** |
| * @param parameters the raw parameters |
| * @param prefix the prefix |
| * @return the parameters whose raw key will replace "-" to "." |
| * @revised 2.7.8 "private" to be "protected" |
| */ |
| protected static Map<String, String> convert(Map<String, String> parameters, String prefix) { |
| if (parameters == null || parameters.isEmpty()) { |
| return new HashMap<>(); |
| } |
| |
| Map<String, String> result = new HashMap<>(); |
| String pre = (StringUtils.isNotEmpty(prefix) ? prefix + "." : ""); |
| for (Map.Entry<String, String> entry : parameters.entrySet()) { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| result.put(pre + key, value); |
| // For compatibility, key like "registry-type" will have a duplicate key "registry.type" |
| if (Arrays.binarySearch(Constants.DOT_COMPATIBLE_KEYS, key) >= 0) { |
| result.put(pre + key.replace('-', '.'), value); |
| } |
| } |
| return result; |
| } |
| |
| public ApplicationModel getApplicationModel() { |
| if (scopeModel instanceof ApplicationModel) { |
| return (ApplicationModel) scopeModel; |
| } else if (scopeModel instanceof ModuleModel) { |
| return ((ModuleModel) scopeModel).getApplicationModel(); |
| } else { |
| throw new IllegalStateException("scope model is invalid: " + scopeModel); |
| } |
| } |
| |
| public ScopeModel getScopeModel() { |
| return scopeModel; |
| } |
| |
| public final void setScopeModel(ScopeModel scopeModel) { |
| if (this.scopeModel != scopeModel) { |
| checkScopeModel(scopeModel); |
| ScopeModel oldScopeModel = this.scopeModel; |
| this.scopeModel = scopeModel; |
| // reinitialize spi extension and change referenced config's scope model |
| this.postProcessAfterScopeModelChanged(oldScopeModel, this.scopeModel); |
| } |
| } |
| |
| protected void checkScopeModel(ScopeModel scopeModel) { |
| if (scopeModel == null) { |
| throw new IllegalArgumentException("scopeModel cannot be null"); |
| } |
| if (!(scopeModel instanceof ApplicationModel)) { |
| throw new IllegalArgumentException("Invalid scope model, expect to be a ApplicationModel but got: " + scopeModel); |
| } |
| } |
| |
| /** |
| * Subclass should override this method to initialize its SPI extensions and change referenced config's scope model. |
| * <p> |
| * For example: |
| * <pre> |
| * protected void postProcessAfterScopeModelChanged() { |
| * super.postProcessAfterScopeModelChanged(); |
| * // re-initialize spi extension |
| * this.protocol = this.getExtensionLoader(Protocol.class).getAdaptiveExtension(); |
| * // change referenced config's scope model |
| * if (this.providerConfig != null && this.providerConfig.getScopeModel() != scopeModel) { |
| * this.providerConfig.setScopeModel(scopeModel); |
| * } |
| * } |
| * </pre> |
| * |
| * @param oldScopeModel |
| * @param newScopeModel |
| */ |
| protected void postProcessAfterScopeModelChanged(ScopeModel oldScopeModel, ScopeModel newScopeModel) { |
| // remove this config from old ConfigManager |
| // if (oldScopeModel != null && oldScopeModel instanceof ApplicationModel) { |
| // ((ApplicationModel)oldScopeModel).getApplicationConfigManager().removeConfig(this); |
| // } |
| } |
| |
| protected <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { |
| if (scopeModel == null) { |
| throw new IllegalStateException("scopeModel is not initialized"); |
| } |
| return scopeModel.getExtensionLoader(type); |
| } |
| |
| @Parameter(excluded = true) |
| public String getId() { |
| return id; |
| } |
| |
| public void setId(String id) { |
| this.id = id; |
| } |
| |
| /** |
| * Copy attributes from annotation |
| * |
| * @param annotationClass |
| * @param annotation |
| */ |
| protected void appendAnnotation(Class<?> annotationClass, Object annotation) { |
| Method[] methods = annotationClass.getMethods(); |
| for (Method method : methods) { |
| if (method.getDeclaringClass() != Object.class |
| && method.getDeclaringClass() != Annotation.class |
| && method.getReturnType() != void.class |
| && method.getParameterTypes().length == 0 |
| && Modifier.isPublic(method.getModifiers()) |
| && !Modifier.isStatic(method.getModifiers())) { |
| try { |
| String property = method.getName(); |
| if ("interfaceClass".equals(property) || "interfaceName".equals(property)) { |
| property = "interface"; |
| } |
| String setter = calculatePropertyToSetter(property); |
| Object value = method.invoke(annotation); |
| if (value != null && !value.equals(method.getDefaultValue())) { |
| Class<?> parameterType = ReflectUtils.getBoxedClass(method.getReturnType()); |
| if ("filter".equals(property) || "listener".equals(property)) { |
| parameterType = String.class; |
| value = StringUtils.join((String[]) value, ","); |
| } else if ("parameters".equals(property)) { |
| parameterType = Map.class; |
| value = CollectionUtils.toStringMap((String[]) value); |
| } |
| try { |
| Method setterMethod = getClass().getMethod(setter, parameterType); |
| setterMethod.invoke(this, value); |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| } |
| } catch (Throwable e) { |
| logger.error(e.getMessage(), e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * <p> |
| * <b>The new instance of the AbstractConfig subclass should return empty metadata.</b> |
| * The purpose is to get the attributes set by the user instead of the default value when the {@link #refresh()} method handles attribute overrides. |
| * </p> |
| * |
| * <p><b>The default value of the field should be set in the {@link #checkDefault()} method</b>, |
| * which will be called at the end of {@link #refresh()}, so that it will not affect the behavior of attribute overrides.</p> |
| * |
| * <p></p> |
| * Should be called after Config was fully initialized. |
| * <p> |
| * Notice! This method should include all properties in the returning map, treat @Parameter differently compared to appendParameters? |
| * </p> |
| * // FIXME: this method should be completely replaced by appendParameters? |
| * // -- Url parameter may use key, but props override only use property name. So replace it with appendAttributes(). |
| * |
| * @see AbstractConfig#checkDefault() |
| * @see AbstractConfig#appendParameters(Map, Object, String) |
| */ |
| public Map<String, String> getMetaData() { |
| return getMetaData(null); |
| } |
| |
| public Map<String, String> getMetaData(String prefix) { |
| Map<String, String> metaData = new HashMap<>(); |
| appendAttributes(metaData, this, prefix); |
| return metaData; |
| } |
| |
| private static BeanInfo getBeanInfo(Class cls) { |
| BeanInfo beanInfo; |
| try { |
| beanInfo = Introspector.getBeanInfo(cls); |
| } catch (IntrospectionException e) { |
| throw new IllegalStateException(e.getMessage(), e); |
| } |
| return beanInfo; |
| } |
| |
| private static boolean isWritableProperty(BeanInfo beanInfo, String key) { |
| for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) { |
| if (key.equals(propertyDescriptor.getName())) { |
| return propertyDescriptor.getWriteMethod() != null; |
| } |
| } |
| return false; |
| } |
| |
| @Parameter(excluded = true, attribute = false) |
| public List<String> getPrefixes() { |
| List<String> prefixes = new ArrayList<>(); |
| if (StringUtils.hasText(this.getId())) { |
| // dubbo.{tag-name}s.{id} |
| prefixes.add(CommonConstants.DUBBO + "." + getPluralTagName(this.getClass()) + "." + this.getId()); |
| } |
| |
| // check name |
| String name = ReflectUtils.getProperty(this, "getName"); |
| if (StringUtils.hasText(name)) { |
| // dubbo.{tag-name}s.{name} |
| String prefix = CommonConstants.DUBBO + "." + getPluralTagName(this.getClass()) + "." + name; |
| if (!prefixes.contains(prefix)) { |
| prefixes.add(prefix); |
| } |
| } |
| |
| // dubbo.{tag-name} |
| prefixes.add(getTypePrefix(this.getClass())); |
| return prefixes; |
| } |
| |
| public static String getTypePrefix(Class<? extends AbstractConfig> cls) { |
| return CommonConstants.DUBBO + "." + getTagName(cls); |
| } |
| |
| public ConfigMode getConfigMode() { |
| return getApplicationModel().getApplicationConfigManager().getConfigMode(); |
| } |
| |
| public void overrideWithConfig(AbstractConfig newOne, boolean overrideAll) { |
| if (!Objects.equals(this.getClass(), newOne.getClass())) { |
| // ignore if two config is not the same class |
| return; |
| } |
| |
| List<Method> methods = MethodUtils.getMethods(this.getClass(), method -> method.getDeclaringClass() != Object.class); |
| for (Method method : methods) { |
| try { |
| Method getterMethod; |
| try { |
| String propertyName = extractPropertyName(method.getName()); |
| String getterName = calculatePropertyToGetter(propertyName); |
| getterMethod = this.getClass().getDeclaredMethod(getterName); |
| } catch (Exception ignore) { |
| continue; |
| } |
| |
| if (MethodUtils.isSetter(method)) { |
| Object oldOne = getterMethod.invoke(this); |
| |
| // if old one is null or need to override |
| if (overrideAll || oldOne == null) { |
| Object newResult = getterMethod.invoke(newOne); |
| // if new one is non-null and new one is not equals old one |
| if (newResult != null && !Objects.equals(newResult, oldOne)) { |
| method.invoke(this, newResult); |
| } |
| } |
| } else if (isParametersSetter(method)) { |
| Object oldOne = getterMethod.invoke(this); |
| Object newResult = getterMethod.invoke(newOne); |
| |
| Map<String, String> oldMap = null; |
| if (oldOne instanceof Map) { |
| oldMap = (Map) oldOne; |
| } |
| |
| Map<String, String> newMap = null; |
| if (newResult instanceof Map) { |
| newMap = (Map) newResult; |
| } |
| |
| // if new map is null, skip |
| if (newMap == null) { |
| continue; |
| } |
| |
| // if old map is null, override with new map |
| if (oldMap == null) { |
| invokeSetParameters(newMap, this); |
| continue; |
| } |
| |
| // if mode is OVERRIDE_IF_ABSENT, put all old map entries to new map, will override the same key |
| // if mode is OVERRIDE_ALL, put all keyed entries not in new map from old map to new map (ignore the same key appeared in old map) |
| if (overrideAll) { |
| oldMap.forEach(newMap::putIfAbsent); |
| } else { |
| newMap.putAll(oldMap); |
| } |
| |
| invokeSetParameters(newMap, this); |
| } else if (isNestedSetter(this, method)) { |
| // not support |
| } |
| |
| } catch (Throwable t) { |
| logger.error("Failed to override field value of config bean: " + this, t); |
| throw new IllegalStateException("Failed to override field value of config bean: " + this, t); |
| } |
| } |
| } |
| |
| /** |
| * Dubbo config property override |
| */ |
| public void refresh() { |
| try { |
| // check and init before do refresh |
| preProcessRefresh(); |
| |
| Environment environment = getScopeModel().getModelEnvironment(); |
| List<Map<String, String>> configurationMaps = environment.getConfigurationMaps(); |
| |
| // Search props starts with PREFIX in order |
| String preferredPrefix = null; |
| List<String> prefixes = getPrefixes(); |
| for (String prefix : prefixes) { |
| if (ConfigurationUtils.hasSubProperties(configurationMaps, prefix)) { |
| preferredPrefix = prefix; |
| break; |
| } |
| } |
| if (preferredPrefix == null) { |
| preferredPrefix = prefixes.get(0); |
| } |
| // Extract sub props (which key was starts with preferredPrefix) |
| Collection<Map<String, String>> instanceConfigMaps = environment.getConfigurationMaps(this, preferredPrefix); |
| Map<String, String> subProperties = ConfigurationUtils.getSubProperties(instanceConfigMaps, preferredPrefix); |
| InmemoryConfiguration subPropsConfiguration = new InmemoryConfiguration(subProperties); |
| |
| if (logger.isDebugEnabled()) { |
| String idOrName = ""; |
| if (StringUtils.hasText(this.getId())) { |
| idOrName = "[id=" + this.getId() + "]"; |
| } else { |
| String name = ReflectUtils.getProperty(this, "getName"); |
| if (StringUtils.hasText(name)) { |
| idOrName = "[name=" + name + "]"; |
| } |
| } |
| logger.debug("Refreshing " + this.getClass().getSimpleName() + idOrName + |
| " with prefix [" + preferredPrefix + |
| "], extracted props: " + subProperties); |
| } |
| |
| assignProperties(this, environment, subProperties, subPropsConfiguration); |
| |
| // process extra refresh of subclass, e.g. refresh method configs |
| processExtraRefresh(preferredPrefix, subPropsConfiguration); |
| |
| } catch (Exception e) { |
| logger.error("Failed to override field value of config bean: " + this, e); |
| throw new IllegalStateException("Failed to override field value of config bean: " + this, e); |
| } |
| |
| postProcessRefresh(); |
| refreshed.set(true); |
| } |
| |
| private void assignProperties(Object obj, Environment environment, Map<String, String> properties, InmemoryConfiguration configuration) { |
| // if old one (this) contains non-null value, do not override |
| boolean overrideIfAbsent = getConfigMode() == ConfigMode.OVERRIDE_IF_ABSENT; |
| |
| // even if old one (this) contains non-null value, do override |
| boolean overrideAll = getConfigMode() == ConfigMode.OVERRIDE_ALL; |
| |
| // loop methods, get override value and set the new value back to method |
| List<Method> methods = MethodUtils.getMethods(obj.getClass(), method -> method.getDeclaringClass() != Object.class); |
| Method[] methodsList = this.getClass().getDeclaredMethods(); |
| for (Method method : methods) { |
| if (MethodUtils.isSetter(method)) { |
| String propertyName = extractPropertyName(method.getName()); |
| |
| // if config mode is OVERRIDE_IF_ABSENT and property has set, skip |
| if (overrideIfAbsent && isPropertySet(methodsList, propertyName)) { |
| continue; |
| } |
| |
| // convert camelCase/snake_case to kebab-case |
| String kebabPropertyName = StringUtils.convertToSplitName(propertyName, "-"); |
| |
| try { |
| String value = StringUtils.trim(configuration.getString(kebabPropertyName)); |
| // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig. |
| if (StringUtils.hasText(value) |
| && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value) |
| && !isIgnoredAttribute(obj.getClass(), propertyName)) { |
| value = environment.resolvePlaceholders(value); |
| method.invoke(obj, ClassUtils.convertPrimitive(ScopeModelUtil.getFrameworkModel(getScopeModel()), method.getParameterTypes()[0], value)); |
| } |
| } catch (Exception e) { |
| logger.info("Failed to override the property " + method.getName() + " in " + |
| obj.getClass().getSimpleName() + |
| ", please make sure every property has getter/setter method provided."); |
| } |
| } else if (isParametersSetter(method)) { |
| String propertyName = extractPropertyName(method.getName()); |
| |
| String value = StringUtils.trim(configuration.getString(propertyName)); |
| Map<String, String> parameterMap; |
| if (StringUtils.hasText(value)) { |
| parameterMap = StringUtils.parseParameters(value); |
| } else { |
| // in this case, maybe parameters.item3=value3. |
| parameterMap = ConfigurationUtils.getSubProperties(properties, PARAMETERS); |
| } |
| Map<String, String> newMap = convert(parameterMap, ""); |
| if (CollectionUtils.isEmptyMap(newMap)) { |
| continue; |
| } |
| |
| // get old map from original obj |
| Map<String, String> oldMap = null; |
| try { |
| String getterName = calculatePropertyToGetter(propertyName); |
| Method getterMethod = this.getClass().getDeclaredMethod(getterName); |
| Object oldOne = getterMethod.invoke(this); |
| if (oldOne instanceof Map) { |
| oldMap = (Map) oldOne; |
| } |
| } catch (Exception ignore) { |
| |
| } |
| |
| // if old map is null, directly set params |
| if (oldMap == null) { |
| invokeSetParameters(newMap, obj); |
| continue; |
| } |
| |
| // if mode is OVERRIDE_IF_ABSENT, put all old map entries to new map, will override the same key |
| // if mode is OVERRIDE_ALL, put all keyed entries not in new map from old map to new map (ignore the same key appeared in old map) |
| // if mode is others, override with new map |
| if (overrideIfAbsent) { |
| newMap.putAll(oldMap); |
| } else if (overrideAll) { |
| oldMap.forEach(newMap::putIfAbsent); |
| } |
| |
| invokeSetParameters(newMap, obj); |
| } else if (isNestedSetter(obj, method)) { |
| try { |
| Class<?> clazz = method.getParameterTypes()[0]; |
| Object inner = clazz.getDeclaredConstructor().newInstance(); |
| String fieldName = MethodUtils.extractFieldName(method); |
| Map<String, String> subProperties = ConfigurationUtils.getSubProperties(properties, fieldName); |
| InmemoryConfiguration subPropsConfiguration = new InmemoryConfiguration(subProperties); |
| assignProperties(inner, environment, subProperties, subPropsConfiguration); |
| method.invoke(obj, inner); |
| } catch (ReflectiveOperationException e) { |
| throw new IllegalStateException("Cannot assign nested class when refreshing config: " + obj.getClass().getName(), e); |
| } |
| } |
| } |
| } |
| |
| private boolean isPropertySet(Method[] methods, String propertyName) { |
| try { |
| String getterName = calculatePropertyToGetter(propertyName); |
| Method getterMethod = findGetMethod(methods,getterName); |
| if (getterMethod == null) { |
| return false; |
| } |
| Object oldOne = getterMethod.invoke(this); |
| if (oldOne != null) { |
| return true; |
| } |
| } catch (Exception ignore) { |
| |
| } |
| return false; |
| } |
| |
| private Method findGetMethod(Method[] methods, String methodName) { |
| for (Method method : methods) { |
| if (method.getName().equals(methodName) && method.getParameterCount() == 0) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| private void invokeSetParameters(Map<String, String> values, Object obj) { |
| if (CollectionUtils.isEmptyMap(values)) { |
| return; |
| } |
| Map<String, String> map = new HashMap<>(); |
| Map<String, String> getParametersMap = invokeGetParameters(obj.getClass(), obj); |
| if (getParametersMap != null && !getParametersMap.isEmpty()) { |
| map.putAll(getParametersMap); |
| } |
| map.putAll(values); |
| invokeSetParameters(obj.getClass(), obj, map); |
| } |
| |
| private boolean isIgnoredAttribute(Class<?> clazz, String propertyName) { |
| Method getter = null; |
| String capitalizePropertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); |
| try { |
| getter = clazz.getMethod("get" + capitalizePropertyName); |
| } catch (NoSuchMethodException e) { |
| try { |
| getter = clazz.getMethod("is" + capitalizePropertyName); |
| } catch (NoSuchMethodException ex) { |
| // ignore |
| } |
| } |
| |
| if (getter == null) { |
| // no getter method |
| return true; |
| } |
| |
| Parameter parameter = getter.getAnnotation(Parameter.class); |
| // not an attribute |
| return parameter != null && !parameter.attribute(); |
| } |
| |
| protected void processExtraRefresh(String preferredPrefix, InmemoryConfiguration subPropsConfiguration) { |
| // process extra refresh |
| } |
| |
| protected void preProcessRefresh() { |
| // pre-process refresh |
| } |
| |
| protected void postProcessRefresh() { |
| // post-process refresh |
| checkDefault(); |
| } |
| |
| /** |
| * Check and set default value for some fields. |
| * <p> |
| * This method will be called at the end of {@link #refresh()}, as a post-initializer. |
| * </p> |
| * <p>NOTE: </p> |
| * <p> |
| * To distinguish between user-set property values and default property values, |
| * do not initialize default value at field declare statement. <b>If the field has a default value, |
| * it should be set in the checkDefault() method</b>, which will be called at the end of {@link #refresh()}, |
| * so that it will not affect the behavior of attribute overrides.</p> |
| * |
| * @see AbstractConfig#getMetaData() |
| * @see AbstractConfig#appendAttributes(Map, Object) |
| */ |
| protected void checkDefault() { |
| } |
| |
| @Parameter(excluded = true, attribute = false) |
| public boolean isRefreshed() { |
| return refreshed.get(); |
| } |
| |
| /** |
| * FIXME check @Parameter(required=true) and any conditions that need to match. |
| */ |
| @Parameter(excluded = true, attribute = false) |
| public boolean isValid() { |
| return true; |
| } |
| |
| @Parameter(excluded = true, attribute = false) |
| public Boolean isDefault() { |
| return isDefault; |
| } |
| |
| public void setDefault(Boolean isDefault) { |
| this.isDefault = isDefault; |
| } |
| |
| @Override |
| public String toString() { |
| try { |
| |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<dubbo:"); |
| buf.append(getTagName(getClass())); |
| for (Method method : getAttributedMethods()) { |
| try { |
| String name = method.getName(); |
| String key = calculateAttributeFromGetter(name); |
| Object value = method.invoke(this); |
| if (value != null) { |
| buf.append(' '); |
| buf.append(key); |
| buf.append("=\""); |
| buf.append(key.equals("password") ? "******" : value); |
| buf.append('\"'); |
| } |
| } catch (Exception e) { |
| logger.warn(e.getMessage(), e); |
| } |
| } |
| buf.append(" />"); |
| return buf.toString(); |
| } catch (Throwable t) { |
| logger.warn(t.getMessage(), t); |
| return super.toString(); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || obj.getClass() != this.getClass()) { |
| return false; |
| } |
| if (obj == this) { |
| return true; |
| } |
| |
| for (Method method : getAttributedMethods()) { |
| // ignore compare 'id' value |
| if ("getId".equals(method.getName())) { |
| continue; |
| } |
| try { |
| Object value1 = method.invoke(this); |
| Object value2 = method.invoke(obj); |
| if (!Objects.equals(value1, value2)) { |
| return false; |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException("compare config instances failed", e); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashCode = 1; |
| |
| for (Method method : getAttributedMethods()) { |
| // ignore compare 'id' value |
| if ("getId".equals(method.getName())) { |
| continue; |
| } |
| try { |
| Object value = method.invoke(this); |
| if (value != null) { |
| hashCode = 31 * hashCode + value.hashCode(); |
| } |
| } catch (Exception ignored) { |
| //ignored |
| } |
| } |
| |
| if (hashCode == 0) { |
| hashCode = 1; |
| } |
| return hashCode; |
| } |
| |
| private List<Method> getAttributedMethods() { |
| Class<? extends AbstractConfig> cls = this.getClass(); |
| return attributedMethodCache.computeIfAbsent(cls, (key) -> computeAttributedMethods()); |
| } |
| |
| /** |
| * compute attributed getter methods, subclass can override this method to add/remove attributed methods |
| * |
| * @return |
| */ |
| protected List<Method> computeAttributedMethods() { |
| Class<? extends AbstractConfig> cls = this.getClass(); |
| BeanInfo beanInfo = getBeanInfo(cls); |
| List<Method> methods = new ArrayList<>(beanInfo.getMethodDescriptors().length); |
| for (MethodDescriptor methodDescriptor : beanInfo.getMethodDescriptors()) { |
| Method method = methodDescriptor.getMethod(); |
| if (MethodUtils.isGetter(method) || isParametersGetter(method)) { |
| // filter non attribute |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (parameter != null && !parameter.attribute()) { |
| continue; |
| } |
| String propertyName = calculateAttributeFromGetter(method.getName()); |
| // filter non-writable property, exclude non property methods, fix #4225 |
| if (!isWritableProperty(beanInfo, propertyName)) { |
| continue; |
| } |
| methods.add(method); |
| } |
| } |
| return methods; |
| } |
| |
| protected ConfigManager getConfigManager() { |
| return getApplicationModel().getApplicationConfigManager(); |
| } |
| } |