| /* |
| * 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 com.alibaba.dubbo.config.spring.schema; |
| |
| import com.alibaba.dubbo.common.Constants; |
| 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.ReflectUtils; |
| import com.alibaba.dubbo.common.utils.StringUtils; |
| import com.alibaba.dubbo.config.ArgumentConfig; |
| import com.alibaba.dubbo.config.ConsumerConfig; |
| import com.alibaba.dubbo.config.MethodConfig; |
| import com.alibaba.dubbo.config.ProtocolConfig; |
| import com.alibaba.dubbo.config.ProviderConfig; |
| import com.alibaba.dubbo.config.RegistryConfig; |
| import com.alibaba.dubbo.config.spring.ReferenceBean; |
| import com.alibaba.dubbo.config.spring.ServiceBean; |
| import com.alibaba.dubbo.rpc.Protocol; |
| |
| import org.springframework.beans.PropertyValue; |
| import org.springframework.beans.factory.config.BeanDefinition; |
| import org.springframework.beans.factory.config.BeanDefinitionHolder; |
| import org.springframework.beans.factory.config.RuntimeBeanReference; |
| import org.springframework.beans.factory.config.TypedStringValue; |
| import org.springframework.beans.factory.support.ManagedList; |
| import org.springframework.beans.factory.support.ManagedMap; |
| import org.springframework.beans.factory.support.RootBeanDefinition; |
| import org.springframework.beans.factory.xml.BeanDefinitionParser; |
| import org.springframework.beans.factory.xml.ParserContext; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.regex.Pattern; |
| |
| /** |
| * AbstractBeanDefinitionParser |
| * |
| * @export |
| */ |
| public class DubboBeanDefinitionParser implements BeanDefinitionParser { |
| |
| private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class); |
| private static final Pattern GROUP_AND_VERION = Pattern.compile("^[\\-.0-9_a-zA-Z]+(\\:[\\-.0-9_a-zA-Z]+)?$"); |
| private final Class<?> beanClass; |
| private final boolean required; |
| |
| public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) { |
| this.beanClass = beanClass; |
| this.required = required; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { |
| RootBeanDefinition beanDefinition = new RootBeanDefinition(); |
| beanDefinition.setBeanClass(beanClass); |
| beanDefinition.setLazyInit(false); |
| String id = element.getAttribute("id"); |
| if ((id == null || id.length() == 0) && required) { |
| String generatedBeanName = element.getAttribute("name"); |
| if (generatedBeanName == null || generatedBeanName.length() == 0) { |
| if (ProtocolConfig.class.equals(beanClass)) { |
| generatedBeanName = "dubbo"; |
| } else { |
| generatedBeanName = element.getAttribute("interface"); |
| } |
| } |
| if (generatedBeanName == null || generatedBeanName.length() == 0) { |
| generatedBeanName = beanClass.getName(); |
| } |
| id = generatedBeanName; |
| int counter = 2; |
| while (parserContext.getRegistry().containsBeanDefinition(id)) { |
| id = generatedBeanName + (counter++); |
| } |
| } |
| if (id != null && id.length() > 0) { |
| if (parserContext.getRegistry().containsBeanDefinition(id)) { |
| throw new IllegalStateException("Duplicate spring bean id " + id); |
| } |
| parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); |
| beanDefinition.getPropertyValues().addPropertyValue("id", id); |
| } |
| if (ProtocolConfig.class.equals(beanClass)) { |
| for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { |
| BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); |
| PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol"); |
| if (property != null) { |
| Object value = property.getValue(); |
| if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) { |
| definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id)); |
| } |
| } |
| } |
| } else if (ServiceBean.class.equals(beanClass)) { |
| String className = element.getAttribute("class"); |
| if (className != null && className.length() > 0) { |
| RootBeanDefinition classDefinition = new RootBeanDefinition(); |
| classDefinition.setBeanClass(ReflectUtils.forName(className)); |
| classDefinition.setLazyInit(false); |
| parseProperties(element.getChildNodes(), classDefinition); |
| beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl")); |
| } |
| } else if (ProviderConfig.class.equals(beanClass)) { |
| parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition); |
| } else if (ConsumerConfig.class.equals(beanClass)) { |
| parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition); |
| } |
| Set<String> props = new HashSet<String>(); |
| ManagedMap parameters = null; |
| for (Method setter : beanClass.getMethods()) { |
| String name = setter.getName(); |
| if (name.length() > 3 && name.startsWith("set") |
| && Modifier.isPublic(setter.getModifiers()) |
| && setter.getParameterTypes().length == 1) { |
| Class<?> type = setter.getParameterTypes()[0]; |
| String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-"); |
| props.add(property); |
| Method getter = null; |
| try { |
| getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]); |
| } catch (NoSuchMethodException e) { |
| try { |
| getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]); |
| } catch (NoSuchMethodException e2) { |
| } |
| } |
| if (getter == null |
| || !Modifier.isPublic(getter.getModifiers()) |
| || !type.equals(getter.getReturnType())) { |
| continue; |
| } |
| if ("parameters".equals(property)) { |
| parameters = parseParameters(element.getChildNodes(), beanDefinition); |
| } else if ("methods".equals(property)) { |
| parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); |
| } else if ("arguments".equals(property)) { |
| parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); |
| } else { |
| String value = element.getAttribute(property); |
| if (value != null) { |
| value = value.trim(); |
| if (value.length() > 0) { |
| if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) { |
| RegistryConfig registryConfig = new RegistryConfig(); |
| registryConfig.setAddress(RegistryConfig.NO_AVAILABLE); |
| beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig); |
| } else if ("registry".equals(property) && value.indexOf(',') != -1) { |
| parseMultiRef("registries", value, beanDefinition, parserContext); |
| } else if ("provider".equals(property) && value.indexOf(',') != -1) { |
| parseMultiRef("providers", value, beanDefinition, parserContext); |
| } else if ("protocol".equals(property) && value.indexOf(',') != -1) { |
| parseMultiRef("protocols", value, beanDefinition, parserContext); |
| } else { |
| Object reference; |
| if (isPrimitive(type)) { |
| if ("async".equals(property) && "false".equals(value) |
| || "timeout".equals(property) && "0".equals(value) |
| || "delay".equals(property) && "0".equals(value) |
| || "version".equals(property) && "0.0.0".equals(value) |
| || "stat".equals(property) && "-1".equals(value) |
| || "reliable".equals(property) && "false".equals(value)) { |
| // backward compatibility for the default value in old version's xsd |
| value = null; |
| } |
| reference = value; |
| } else if ("protocol".equals(property) |
| && ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value) |
| && (!parserContext.getRegistry().containsBeanDefinition(value) |
| || !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) { |
| if ("dubbo:provider".equals(element.getTagName())) { |
| logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />"); |
| } |
| // backward compatibility |
| ProtocolConfig protocol = new ProtocolConfig(); |
| protocol.setName(value); |
| reference = protocol; |
| } else if ("onreturn".equals(property)) { |
| int index = value.lastIndexOf("."); |
| String returnRef = value.substring(0, index); |
| String returnMethod = value.substring(index + 1); |
| reference = new RuntimeBeanReference(returnRef); |
| beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod); |
| } else if ("onthrow".equals(property)) { |
| int index = value.lastIndexOf("."); |
| String throwRef = value.substring(0, index); |
| String throwMethod = value.substring(index + 1); |
| reference = new RuntimeBeanReference(throwRef); |
| beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod); |
| } else if ("oninvoke".equals(property)) { |
| int index = value.lastIndexOf("."); |
| String invokeRef = value.substring(0, index); |
| String invokeRefMethod = value.substring(index + 1); |
| reference = new RuntimeBeanReference(invokeRef); |
| beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod); |
| } else { |
| if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) { |
| BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value); |
| if (!refBean.isSingleton()) { |
| throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>"); |
| } |
| } |
| reference = new RuntimeBeanReference(value); |
| } |
| beanDefinition.getPropertyValues().addPropertyValue(property, reference); |
| } |
| } |
| } |
| } |
| } |
| } |
| NamedNodeMap attributes = element.getAttributes(); |
| int len = attributes.getLength(); |
| for (int i = 0; i < len; i++) { |
| Node node = attributes.item(i); |
| String name = node.getLocalName(); |
| if (!props.contains(name)) { |
| if (parameters == null) { |
| parameters = new ManagedMap(); |
| } |
| String value = node.getNodeValue(); |
| parameters.put(name, new TypedStringValue(value, String.class)); |
| } |
| } |
| if (parameters != null) { |
| beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters); |
| } |
| return beanDefinition; |
| } |
| |
| private static boolean isPrimitive(Class<?> cls) { |
| return cls.isPrimitive() || cls == Boolean.class || cls == Byte.class |
| || cls == Character.class || cls == Short.class || cls == Integer.class |
| || cls == Long.class || cls == Float.class || cls == Double.class |
| || cls == String.class || cls == Date.class || cls == Class.class; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static void parseMultiRef(String property, String value, RootBeanDefinition beanDefinition, |
| ParserContext parserContext) { |
| String[] values = value.split("\\s*[,]+\\s*"); |
| ManagedList list = null; |
| for (int i = 0; i < values.length; i++) { |
| String v = values[i]; |
| if (v != null && v.length() > 0) { |
| if (list == null) { |
| list = new ManagedList(); |
| } |
| list.add(new RuntimeBeanReference(v)); |
| } |
| } |
| beanDefinition.getPropertyValues().addPropertyValue(property, list); |
| } |
| |
| private static void parseNested(Element element, ParserContext parserContext, Class<?> beanClass, boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) { |
| NodeList nodeList = element.getChildNodes(); |
| if (nodeList != null && nodeList.getLength() > 0) { |
| boolean first = true; |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element) { |
| if (tag.equals(node.getNodeName()) |
| || tag.equals(node.getLocalName())) { |
| if (first) { |
| first = false; |
| String isDefault = element.getAttribute("default"); |
| if (isDefault == null || isDefault.length() == 0) { |
| beanDefinition.getPropertyValues().addPropertyValue("default", "false"); |
| } |
| } |
| BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required); |
| if (subDefinition != null && ref != null && ref.length() > 0) { |
| subDefinition.getPropertyValues().addPropertyValue(property, new RuntimeBeanReference(ref)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) { |
| if (nodeList != null && nodeList.getLength() > 0) { |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element) { |
| if ("property".equals(node.getNodeName()) |
| || "property".equals(node.getLocalName())) { |
| String name = ((Element) node).getAttribute("name"); |
| if (name != null && name.length() > 0) { |
| String value = ((Element) node).getAttribute("value"); |
| String ref = ((Element) node).getAttribute("ref"); |
| if (value != null && value.length() > 0) { |
| beanDefinition.getPropertyValues().addPropertyValue(name, value); |
| } else if (ref != null && ref.length() > 0) { |
| beanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref)); |
| } else { |
| throw new UnsupportedOperationException("Unsupported <property name=\"" + name + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\"" + name + "\" value=\"...\" />"); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static ManagedMap parseParameters(NodeList nodeList, RootBeanDefinition beanDefinition) { |
| if (nodeList != null && nodeList.getLength() > 0) { |
| ManagedMap parameters = null; |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element) { |
| if ("parameter".equals(node.getNodeName()) |
| || "parameter".equals(node.getLocalName())) { |
| if (parameters == null) { |
| parameters = new ManagedMap(); |
| } |
| String key = ((Element) node).getAttribute("key"); |
| String value = ((Element) node).getAttribute("value"); |
| boolean hide = "true".equals(((Element) node).getAttribute("hide")); |
| if (hide) { |
| key = Constants.HIDE_KEY_PREFIX + key; |
| } |
| parameters.put(key, new TypedStringValue(value, String.class)); |
| } |
| } |
| } |
| return parameters; |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static void parseMethods(String id, NodeList nodeList, RootBeanDefinition beanDefinition, |
| ParserContext parserContext) { |
| if (nodeList != null && nodeList.getLength() > 0) { |
| ManagedList methods = null; |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element) { |
| Element element = (Element) node; |
| if ("method".equals(node.getNodeName()) || "method".equals(node.getLocalName())) { |
| String methodName = element.getAttribute("name"); |
| if (methodName == null || methodName.length() == 0) { |
| throw new IllegalStateException("<dubbo:method> name attribute == null"); |
| } |
| if (methods == null) { |
| methods = new ManagedList(); |
| } |
| BeanDefinition methodBeanDefinition = parse(((Element) node), |
| parserContext, MethodConfig.class, false); |
| String name = id + "." + methodName; |
| BeanDefinitionHolder methodBeanDefinitionHolder = new BeanDefinitionHolder( |
| methodBeanDefinition, name); |
| methods.add(methodBeanDefinitionHolder); |
| } |
| } |
| } |
| if (methods != null) { |
| beanDefinition.getPropertyValues().addPropertyValue("methods", methods); |
| } |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static void parseArguments(String id, NodeList nodeList, RootBeanDefinition beanDefinition, |
| ParserContext parserContext) { |
| if (nodeList != null && nodeList.getLength() > 0) { |
| ManagedList arguments = null; |
| for (int i = 0; i < nodeList.getLength(); i++) { |
| Node node = nodeList.item(i); |
| if (node instanceof Element) { |
| Element element = (Element) node; |
| if ("argument".equals(node.getNodeName()) || "argument".equals(node.getLocalName())) { |
| String argumentIndex = element.getAttribute("index"); |
| if (arguments == null) { |
| arguments = new ManagedList(); |
| } |
| BeanDefinition argumentBeanDefinition = parse(((Element) node), |
| parserContext, ArgumentConfig.class, false); |
| String name = id + "." + argumentIndex; |
| BeanDefinitionHolder argumentBeanDefinitionHolder = new BeanDefinitionHolder( |
| argumentBeanDefinition, name); |
| arguments.add(argumentBeanDefinitionHolder); |
| } |
| } |
| } |
| if (arguments != null) { |
| beanDefinition.getPropertyValues().addPropertyValue("arguments", arguments); |
| } |
| } |
| } |
| |
| @Override |
| public BeanDefinition parse(Element element, ParserContext parserContext) { |
| return parse(element, parserContext, beanClass, required); |
| } |
| |
| } |