/* * Copyright 1999-2011 Alibaba Group. * * 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. */ package com.alibaba.dubbo.config; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.alibaba.dubbo.common.Constants; import com.alibaba.dubbo.common.URL; import com.alibaba.dubbo.common.extension.ExtensionLoader; import com.alibaba.dubbo.common.logger.Logger; import com.alibaba.dubbo.common.logger.LoggerFactory; import com.alibaba.dubbo.common.utils.CollectionUtils; import com.alibaba.dubbo.common.utils.ConfigUtils; import com.alibaba.dubbo.common.utils.ReflectUtils; import com.alibaba.dubbo.common.utils.StringUtils; import com.alibaba.dubbo.config.support.Parameter; /** * 配置解析的工具方法、公共方法 * * @author william.liangf * @export */ public abstract class AbstractConfig implements Serializable { private static final long serialVersionUID = 4267533505537413570L; protected static final Logger logger = LoggerFactory.getLogger(AbstractConfig.class); private static final int MAX_LENGTH = 100; private static final int MAX_PATH_LENGTH = 200; private static final Pattern PATTERN_NAME = Pattern.compile("[\\-._0-9a-zA-Z]+"); private static final Pattern PATTERN_MULTI_NAME = Pattern.compile("[,\\-._0-9a-zA-Z]+"); private static final Pattern PATTERN_METHOD_NAME = Pattern.compile("[a-zA-Z][0-9a-zA-Z]*"); private static final Pattern PATTERN_PATH = Pattern.compile("[/\\-$._0-9a-zA-Z]+"); private static final Pattern PATTERN_NAME_HAS_SYMBOL = Pattern.compile("[:*,/\\-._0-9a-zA-Z]+"); private static final Pattern PATTERN_KEY = Pattern.compile("[*,\\-._0-9a-zA-Z]+"); protected String id; @Parameter(excluded = true) public String getId() { return id; } public void setId(String id) { this.id = id; } private static final Map<String, String> legacyProperties = new HashMap<String, String>(); static { legacyProperties.put("dubbo.protocol.name", "dubbo.service.protocol"); legacyProperties.put("dubbo.protocol.host", "dubbo.service.server.host"); legacyProperties.put("dubbo.protocol.port", "dubbo.service.server.port"); legacyProperties.put("dubbo.protocol.threads", "dubbo.service.max.thread.pool.size"); legacyProperties.put("dubbo.consumer.timeout", "dubbo.service.invoke.timeout"); legacyProperties.put("dubbo.consumer.retries", "dubbo.service.max.retry.providers"); legacyProperties.put("dubbo.consumer.check", "dubbo.service.allow.no.provider"); legacyProperties.put("dubbo.service.url", "dubbo.service.address"); } 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; } 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, new Object[0]); 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, new Class<?>[] { parameterType }); setterMethod.invoke(this, new Object[] { value }); } catch (NoSuchMethodException e) { // ignore } } } catch (Throwable e) { logger.error(e.getMessage(), e); } } } } protected static void appendProperties(AbstractConfig config) { if (config == null) { return; } String prefix = "dubbo." + getTagName(config.getClass()) + "."; Method[] methods = config.getClass().getMethods(); for (Method method : methods) { try { String name = method.getName(); if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 1 && isPrimitive(method.getParameterTypes()[0])) { String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-"); String value = null; if (config.getId() != null && config.getId().length() > 0) { String pn = prefix + config.getId() + "." + property; value = System.getProperty(pn); if(! StringUtils.isBlank(value)) { logger.info("Use System Property " + pn + " to config dubbo"); } } if (value == null || value.length() == 0) { String pn = prefix + property; value = System.getProperty(pn); if(! StringUtils.isBlank(value)) { logger.info("Use System Property " + pn + " to config dubbo"); } } if (value == null || value.length() == 0) { Method getter; try { getter = config.getClass().getMethod("get" + name.substring(3), new Class<?>[0]); } catch (NoSuchMethodException e) { try { getter = config.getClass().getMethod("is" + name.substring(3), new Class<?>[0]); } catch (NoSuchMethodException e2) { getter = null; } } if (getter != null) { if (getter.invoke(config, new Object[0]) == null) { if (config.getId() != null && config.getId().length() > 0) { value = ConfigUtils.getProperty(prefix + config.getId() + "." + property); } if (value == null || value.length() == 0) { value = ConfigUtils.getProperty(prefix + property); } if (value == null || value.length() == 0) { String legacyKey = legacyProperties.get(prefix + property); if (legacyKey != null && legacyKey.length() > 0) { value = convertLegacyValue(legacyKey, ConfigUtils.getProperty(legacyKey)); } } } } } if (value != null && value.length() > 0) { method.invoke(config, new Object[] {convertPrimitive(method.getParameterTypes()[0], value)}); } } } catch (Exception e) { logger.error(e.getMessage(), e); } } } private static String getTagName(Class<?> cls) { String tag = cls.getSimpleName(); for (String suffix : SUFFIXS) { if (tag.endsWith(suffix)) { tag = tag.substring(0, tag.length() - suffix.length()); break; } } tag = tag.toLowerCase(); return tag; } protected static void appendParameters(Map<String, String> parameters, Object config) { appendParameters(parameters, config, null); } @SuppressWarnings("unchecked") protected 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 ((name.startsWith("get") || name.startsWith("is")) && ! "getClass".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { Parameter parameter = method.getAnnotation(Parameter.class); if (method.getReturnType() == Object.class || parameter != null && parameter.excluded()) { continue; } int i = name.startsWith("get") ? 3 : 2; String prop = StringUtils.camelToSplitName(name.substring(i, i + 1).toLowerCase() + name.substring(i + 1), "."); String key; if (parameter != null && parameter.key() != null && parameter.key().length() > 0) { key = parameter.key(); } else { key = prop; } Object value = method.invoke(config, new Object[0]); 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 = (String)parameters.get(Constants.DEFAULT_KEY + "." + key); if (pre != null && pre.length() > 0) { str = pre + "," + str; } pre = (String)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 ("getParameters".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && method.getReturnType() == Map.class) { Map<String, String> map = (Map<String, String>) method.invoke(config, new Object[0]); if (map != null && map.size() > 0) { String pre = (prefix != null && prefix.length() > 0 ? prefix + "." : ""); for (Map.Entry<String, String> entry : map.entrySet()) { parameters.put(pre + entry.getKey().replace('-', '.'), entry.getValue()); } } } } catch (Exception e) { throw new IllegalStateException(e.getMessage(), e); } } } protected static void appendAttributes(Map<Object, Object> parameters, Object config) { appendAttributes(parameters, config, null); } protected static void appendAttributes(Map<Object, Object> parameters, Object config, String prefix) { if (config == null) { return; } Method[] methods = config.getClass().getMethods(); for (Method method : methods) { try { String name = method.getName(); if ((name.startsWith("get") || name.startsWith("is")) && ! "getClass".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { Parameter parameter = method.getAnnotation(Parameter.class); if (parameter == null || !parameter.attribute()) continue; String key; if (parameter != null && parameter.key() != null && parameter.key().length() > 0) { key = parameter.key(); } else { int i = name.startsWith("get") ? 3 : 2; key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1); } Object value = method.invoke(config, new Object[0]); 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); } } } private static boolean isPrimitive(Class<?> type) { return type.isPrimitive() || type == String.class || type == Character.class || type == Boolean.class || type == Byte.class || type == Short.class || type == Integer.class || type == Long.class || type == Float.class || type == Double.class || type == Object.class; } private static Object convertPrimitive(Class<?> type, String value) { if (type == char.class || type == Character.class) { return value.length() > 0 ? value.charAt(0) : '\0'; } else if (type == boolean.class || type == Boolean.class) { return Boolean.valueOf(value); } else if (type == byte.class || type == Byte.class) { return Byte.valueOf(value); } else if (type == short.class || type == Short.class) { return Short.valueOf(value); } else if (type == int.class || type == Integer.class) { return Integer.valueOf(value); } else if (type == long.class || type == Long.class) { return Long.valueOf(value); } else if (type == float.class || type == Float.class) { return Float.valueOf(value); } else if (type == double.class || type == Double.class) { return Double.valueOf(value); } return value; } protected static void checkExtension(Class<?> type, String property, String value) { checkName(property, value); if (value != null && value.length() > 0 && ! ExtensionLoader.getExtensionLoader(type).hasExtension(value)) { throw new IllegalStateException("No such extension " + value + " for " + property + "/" + type.getName()); } } protected static void checkMultiExtension(Class<?> type, String property, String value) { checkMultiName(property, value); if (value != null && value.length() > 0) { String[] values = value.split("\\s*[,]+\\s*"); for (String v : values) { if (v.startsWith(Constants.REMOVE_VALUE_PREFIX)) { v = v.substring(1); } if (Constants.DEFAULT_KEY.equals(v)) { continue; } if (! ExtensionLoader.getExtensionLoader(type).hasExtension(v)) { throw new IllegalStateException("No such extension " + v + " for " + property + "/" + type.getName()); } } } } protected static void checkLength(String property, String value) { checkProperty(property, value, MAX_LENGTH, null); } protected static void checkPathLength(String property, String value) { checkProperty(property, value, MAX_PATH_LENGTH, null); } protected static void checkName(String property, String value) { checkProperty(property, value, MAX_LENGTH, PATTERN_NAME); } protected static void checkNameHasSymbol(String property, String value) { checkProperty(property, value, MAX_LENGTH, PATTERN_NAME_HAS_SYMBOL); } protected static void checkKey(String property, String value) { checkProperty(property, value, MAX_LENGTH, PATTERN_KEY); } protected static void checkMultiName(String property, String value) { checkProperty(property, value, MAX_LENGTH, PATTERN_MULTI_NAME); } protected static void checkPathName(String property, String value) { checkProperty(property, value, MAX_PATH_LENGTH, PATTERN_PATH); } protected static void checkMethodName(String property, String value) { checkProperty(property, value, MAX_LENGTH, PATTERN_METHOD_NAME); } protected static void checkParameterName(Map<String, String> parameters) { if (parameters == null || parameters.size() == 0) { return; } for (Map.Entry<String, String> entry : parameters.entrySet()) { //change by tony.chenl parameter value maybe has colon.for example napoli address checkNameHasSymbol(entry.getKey(), entry.getValue()); } } protected static void checkProperty(String property, String value, int maxlength, Pattern pattern) { if (value == null || value.length() == 0) { return; } if(value.length() > maxlength){ throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" is longer than " + maxlength); } if (pattern != null) { Matcher matcher = pattern.matcher(value); if(! matcher.matches()) { throw new IllegalStateException("Invalid " + property + "=\"" + value + "\" contain illegal charactor, only digit, letter, '-', '_' and '.' is legal."); } } } static { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { if (logger.isInfoEnabled()) { logger.info("Run shutdown hook now."); } ProtocolConfig.destroyAll(); } }, "DubboShutdownHook")); } private static final String[] SUFFIXS = new String[] {"Config", "Bean"}; @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 { String name = method.getName(); if ((name.startsWith("get") || name.startsWith("is")) && ! "getClass".equals(name) && ! "get".equals(name) && ! "is".equals(name) && Modifier.isPublic(method.getModifiers()) && method.getParameterTypes().length == 0 && isPrimitive(method.getReturnType())) { int i = name.startsWith("get") ? 3 : 2; String key = name.substring(i, i + 1).toLowerCase() + name.substring(i + 1); Object value = method.invoke(this, new Object[0]); 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(); } } }