| /* |
| * 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.CompositeConfiguration; |
| import org.apache.dubbo.common.config.Environment; |
| import org.apache.dubbo.common.constants.CommonConstants; |
| 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.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.support.Parameter; |
| import org.apache.dubbo.rpc.model.ApplicationModel; |
| import org.apache.dubbo.rpc.model.AsyncMethodInfo; |
| |
| import javax.annotation.PostConstruct; |
| import java.io.Serializable; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.stream.Collectors; |
| |
| import static org.apache.dubbo.common.utils.ReflectUtils.findMethodByMethodSignature; |
| |
| /** |
| * 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; |
| |
| /** |
| * The legacy properties container |
| */ |
| private static final Map<String, String> LEGACY_PROPERTIES = new HashMap<String, String>(); |
| |
| /** |
| * The suffix container |
| */ |
| private static final String[] SUFFIXES = new String[]{"Config", "Bean", "ConfigBase"}; |
| |
| static { |
| LEGACY_PROPERTIES.put("dubbo.protocol.name", "dubbo.service.protocol"); |
| LEGACY_PROPERTIES.put("dubbo.protocol.host", "dubbo.service.server.host"); |
| LEGACY_PROPERTIES.put("dubbo.protocol.port", "dubbo.service.server.port"); |
| LEGACY_PROPERTIES.put("dubbo.protocol.threads", "dubbo.service.max.thread.pool.size"); |
| LEGACY_PROPERTIES.put("dubbo.consumer.timeout", "dubbo.service.invoke.timeout"); |
| LEGACY_PROPERTIES.put("dubbo.consumer.retries", "dubbo.service.max.retry.providers"); |
| LEGACY_PROPERTIES.put("dubbo.consumer.check", "dubbo.service.allow.no.provider"); |
| LEGACY_PROPERTIES.put("dubbo.service.url", "dubbo.service.address"); |
| } |
| |
| /** |
| * The config id |
| */ |
| protected String id; |
| protected String prefix; |
| |
| protected final AtomicBoolean refreshed = new AtomicBoolean(false); |
| |
| private static String convertLegacyValue(String key, String value) { |
| if (value != null && value.length() > 0) { |
| if ("dubbo.service.max.retry.providers".equals(key)) { |
| return String.valueOf(Integer.parseInt(value) - 1); |
| } else if ("dubbo.service.allow.no.provider".equals(key)) { |
| return String.valueOf(!Boolean.parseBoolean(value)); |
| } |
| } |
| return value; |
| } |
| |
| public static String getTagName(Class<?> cls) { |
| 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 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) { |
| if (config == null) { |
| return; |
| } |
| Method[] methods = config.getClass().getMethods(); |
| for (Method method : methods) { |
| try { |
| String name = method.getName(); |
| if (MethodUtils.isGetter(method)) { |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) { |
| continue; |
| } |
| String key; |
| if (parameter != null && parameter.key().length() > 0) { |
| key = parameter.key(); |
| } else { |
| key = calculatePropertyFromGetter(name); |
| } |
| Object value = method.invoke(config); |
| String str = String.valueOf(value).trim(); |
| if (value != null && str.length() > 0) { |
| if (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; |
| } |
| } |
| if (prefix != null && prefix.length() > 0) { |
| key = prefix + "." + key; |
| } |
| parameters.put(key, str); |
| } else if (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, new Object[0]); |
| parameters.putAll(convert(map, prefix)); |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException(e.getMessage(), e); |
| } |
| } |
| } |
| |
| @Deprecated |
| protected static void appendAttributes(Map<String, Object> parameters, Object config) { |
| appendAttributes(parameters, config, null); |
| } |
| |
| @Deprecated |
| protected static void appendAttributes(Map<String, Object> parameters, Object config, String prefix) { |
| if (config == null) { |
| return; |
| } |
| Method[] methods = config.getClass().getMethods(); |
| for (Method method : methods) { |
| try { |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (parameter == null || !parameter.attribute()) { |
| continue; |
| } |
| String name = method.getName(); |
| if (MethodUtils.isGetter(method)) { |
| String key; |
| if (parameter.key().length() > 0) { |
| key = parameter.key(); |
| } else { |
| key = calculateAttributeFromGetter(name); |
| } |
| Object value = method.invoke(config); |
| if (value != null) { |
| if (prefix != null && prefix.length() > 0) { |
| key = prefix + "." + key; |
| } |
| parameters.put(key, value); |
| } |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException(e.getMessage(), e); |
| } |
| } |
| } |
| |
| protected static AsyncMethodInfo convertMethodConfig2AsyncInfo(MethodConfig methodConfig) { |
| if (methodConfig == null || (methodConfig.getOninvoke() == null && methodConfig.getOnreturn() == null && methodConfig.getOnthrow() == null)) { |
| return null; |
| } |
| |
| //check config conflict |
| if (Boolean.FALSE.equals(methodConfig.isReturn()) && (methodConfig.getOnreturn() != null || methodConfig.getOnthrow() != null)) { |
| throw new IllegalStateException("method config error : return attribute must be set true when onreturn or onthrow has been set."); |
| } |
| |
| AsyncMethodInfo asyncMethodInfo = new AsyncMethodInfo(); |
| |
| asyncMethodInfo.setOninvokeInstance(methodConfig.getOninvoke()); |
| asyncMethodInfo.setOnreturnInstance(methodConfig.getOnreturn()); |
| asyncMethodInfo.setOnthrowInstance(methodConfig.getOnthrow()); |
| |
| try { |
| String oninvokeMethod = methodConfig.getOninvokeMethod(); |
| if (StringUtils.isNotEmpty(oninvokeMethod)) { |
| asyncMethodInfo.setOninvokeMethod(getMethodByName(methodConfig.getOninvoke().getClass(), oninvokeMethod)); |
| } |
| |
| String onreturnMethod = methodConfig.getOnreturnMethod(); |
| if (StringUtils.isNotEmpty(onreturnMethod)) { |
| asyncMethodInfo.setOnreturnMethod(getMethodByName(methodConfig.getOnreturn().getClass(), onreturnMethod)); |
| } |
| |
| String onthrowMethod = methodConfig.getOnthrowMethod(); |
| if (StringUtils.isNotEmpty(onthrowMethod)) { |
| asyncMethodInfo.setOnthrowMethod(getMethodByName(methodConfig.getOnthrow().getClass(), onthrowMethod)); |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException(e.getMessage(), e); |
| } |
| |
| return asyncMethodInfo; |
| } |
| |
| private static Method getMethodByName(Class<?> clazz, String methodName) { |
| try { |
| return ReflectUtils.findMethodByMethodName(clazz, methodName); |
| } catch (Exception e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| protected static Set<String> getSubProperties(Map<String, String> properties, String prefix) { |
| return properties.keySet().stream().filter(k -> k.contains(prefix)).map(k -> { |
| k = k.substring(prefix.length()); |
| return k.substring(0, k.indexOf(".")); |
| }).collect(Collectors.toSet()); |
| } |
| |
| private static String extractPropertyName(Class<?> clazz, Method setter) throws Exception { |
| String propertyName = setter.getName().substring("set".length()); |
| Method getter = null; |
| try { |
| getter = clazz.getMethod("get" + propertyName); |
| } catch (NoSuchMethodException e) { |
| getter = clazz.getMethod("is" + propertyName); |
| } |
| Parameter parameter = getter.getAnnotation(Parameter.class); |
| if (parameter != null && StringUtils.isNotEmpty(parameter.key()) && parameter.useKeyAsProperty()) { |
| propertyName = parameter.key(); |
| } else { |
| propertyName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); |
| } |
| return propertyName; |
| } |
| |
| 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); |
| } |
| |
| /** |
| * @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 Collections.emptyMap(); |
| } |
| |
| Map<String, String> result = new HashMap<>(); |
| String pre = (prefix != null && prefix.length() > 0 ? 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 has a duplicate key "registry.type" |
| if (key.contains("-")) { |
| result.put(pre + key.replace('-', '.'), value); |
| } |
| } |
| return result; |
| } |
| |
| @Parameter(excluded = true) |
| public String getId() { |
| return id; |
| } |
| |
| public void setId(String id) { |
| this.id = id; |
| } |
| |
| public void updateIdIfAbsent(String value) { |
| if (StringUtils.isNotEmpty(value) && StringUtils.isEmpty(id)) { |
| this.id = value; |
| } |
| } |
| |
| protected void appendAnnotation(Class<?> annotationClass, Object annotation) { |
| Method[] methods = annotationClass.getMethods(); |
| for (Method method : methods) { |
| if (method.getDeclaringClass() != Object.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 = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); |
| 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); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Should be called after Config was fully initialized. |
| * // FIXME: this method should be completely replaced by appendParameters |
| * |
| * @return |
| * @see AbstractConfig#appendParameters(Map, Object, String) |
| * <p> |
| * Notice! This method should include all properties in the returning map, treat @Parameter differently compared to appendParameters. |
| */ |
| public Map<String, String> getMetaData() { |
| Map<String, String> metaData = new HashMap<>(); |
| Method[] methods = this.getClass().getMethods(); |
| for (Method method : methods) { |
| try { |
| String name = method.getName(); |
| if (MethodUtils.isMetaMethod(method)) { |
| String key; |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (parameter != null && parameter.key().length() > 0 && parameter.useKeyAsProperty()) { |
| key = parameter.key(); |
| } else { |
| key = calculateAttributeFromGetter(name); |
| } |
| // treat url and configuration differently, the value should always present in configuration though it may not need to present in url. |
| //if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) { |
| if (method.getReturnType() == Object.class) { |
| metaData.put(key, null); |
| continue; |
| } |
| |
| /** |
| * Attributes annotated as deprecated should not override newly added replacement. |
| */ |
| if (MethodUtils.isDeprecated(method) && metaData.get(key) != null) { |
| continue; |
| } |
| |
| Object value = method.invoke(this); |
| String str = String.valueOf(value).trim(); |
| if (value != null && str.length() > 0) { |
| metaData.put(key, str); |
| } else { |
| metaData.put(key, null); |
| } |
| } else if (isParametersGetter(method)) { |
| Map<String, String> map = (Map<String, String>) method.invoke(this, new Object[0]); |
| metaData.putAll(convert(map, "")); |
| } |
| } catch (Exception e) { |
| throw new IllegalStateException(e.getMessage(), e); |
| } |
| } |
| return metaData; |
| } |
| |
| @Parameter(excluded = true) |
| public String getPrefix() { |
| return StringUtils.isNotEmpty(prefix) ? prefix : (CommonConstants.DUBBO + "." + getTagName(this.getClass())); |
| } |
| |
| public void setPrefix(String prefix) { |
| this.prefix = prefix; |
| } |
| |
| public void refresh() { |
| Environment env = ApplicationModel.getEnvironment(); |
| try { |
| CompositeConfiguration compositeConfiguration = env.getPrefixedConfiguration(this); |
| // loop methods, get override value and set the new value back to method |
| Method[] methods = getClass().getMethods(); |
| for (Method method : methods) { |
| if (MethodUtils.isSetter(method)) { |
| try { |
| String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); |
| // isTypeMatch() is called to avoid duplicate and incorrect update, for example, we have two 'setGeneric' methods in ReferenceConfig. |
| if (StringUtils.isNotEmpty(value) && ClassUtils.isTypeMatch(method.getParameterTypes()[0], value)) { |
| method.invoke(this, ClassUtils.convertPrimitive(method.getParameterTypes()[0], value)); |
| } |
| } catch (NoSuchMethodException e) { |
| logger.info("Failed to override the property " + method.getName() + " in " + |
| this.getClass().getSimpleName() + |
| ", please make sure every property has getter/setter method provided."); |
| } |
| } else if (isParametersSetter(method)) { |
| String value = StringUtils.trim(compositeConfiguration.getString(extractPropertyName(getClass(), method))); |
| if (StringUtils.isNotEmpty(value)) { |
| Map<String, String> map = invokeGetParameters(getClass(), this); |
| map = map == null ? new HashMap<>() : map; |
| map.putAll(convert(StringUtils.parseParameters(value), "")); |
| invokeSetParameters(getClass(), this, map); |
| } |
| } |
| } |
| } catch (Exception e) { |
| logger.error("Failed to override ", e); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| try { |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<dubbo:"); |
| buf.append(getTagName(getClass())); |
| Method[] methods = getClass().getMethods(); |
| for (Method method : methods) { |
| try { |
| if (MethodUtils.isGetter(method)) { |
| String name = method.getName(); |
| String key = calculateAttributeFromGetter(name); |
| |
| try { |
| getClass().getDeclaredField(key); |
| } catch (NoSuchFieldException e) { |
| // ignore |
| continue; |
| } |
| |
| Object value = method.invoke(this); |
| if (value != null) { |
| buf.append(" "); |
| buf.append(key); |
| buf.append("=\""); |
| buf.append(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(); |
| } |
| } |
| |
| /** |
| * FIXME check @Parameter(required=true) and any conditions that need to match. |
| */ |
| @Parameter(excluded = true) |
| public boolean isValid() { |
| return true; |
| } |
| |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || !(obj.getClass().getName().equals(this.getClass().getName()))) { |
| return false; |
| } |
| |
| Method[] methods = this.getClass().getMethods(); |
| for (Method method1 : methods) { |
| if (MethodUtils.isGetter(method1)) { |
| Parameter parameter = method1.getAnnotation(Parameter.class); |
| if (parameter != null && parameter.excluded()) { |
| continue; |
| } |
| try { |
| Method method2 = obj.getClass().getMethod(method1.getName(), method1.getParameterTypes()); |
| Object value1 = method1.invoke(this, new Object[]{}); |
| Object value2 = method2.invoke(obj, new Object[]{}); |
| if (!Objects.equals(value1, value2)) { |
| return false; |
| } |
| } catch (Exception e) { |
| return true; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Add {@link AbstractConfig instance} into {@link ConfigManager} |
| * <p> |
| * Current method will invoked by Spring or Java EE container automatically, or should be triggered manually. |
| * |
| * @see ConfigManager#addConfig(AbstractConfig) |
| * @since 2.7.5 |
| */ |
| @PostConstruct |
| public void addIntoConfigManager() { |
| ApplicationModel.getConfigManager().addConfig(this); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hashCode = 1; |
| |
| Method[] methods = this.getClass().getMethods(); |
| for (Method method : methods) { |
| if (MethodUtils.isGetter(method)) { |
| Parameter parameter = method.getAnnotation(Parameter.class); |
| if (parameter != null && parameter.excluded()) { |
| continue; |
| } |
| try { |
| Object value = method.invoke(this, new Object[]{}); |
| hashCode = 31 * hashCode + value.hashCode(); |
| } catch (Exception ignored) { |
| //ignored |
| } |
| } |
| } |
| |
| if (hashCode == 0) { |
| hashCode = 1; |
| } |
| |
| return hashCode; |
| } |
| } |