/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.citrus.springext.impl; import static com.alibaba.citrus.springext.Schema.*; import static com.alibaba.citrus.springext.support.SchemaUtil.*; import static com.alibaba.citrus.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.ObjectUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import static java.util.Collections.*; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; import com.alibaba.citrus.springext.ConfigurationPoint; import com.alibaba.citrus.springext.ConfigurationPointException; import com.alibaba.citrus.springext.ConfigurationPoints; import com.alibaba.citrus.springext.Contribution; import com.alibaba.citrus.springext.ContributionAware; import com.alibaba.citrus.springext.ContributionType; import com.alibaba.citrus.springext.ResourceResolver.PropertyHandler; import com.alibaba.citrus.springext.ResourceResolver.Resource; import com.alibaba.citrus.springext.Schema; import com.alibaba.citrus.springext.SourceInfo; import com.alibaba.citrus.springext.VersionableSchemas; import com.alibaba.citrus.springext.support.ConfigurationPointSourceInfo; import com.alibaba.citrus.springext.support.SourceInfoSupport; import com.alibaba.citrus.springext.support.parser.DefaultElementDefinitionParser; import com.alibaba.citrus.util.ToStringBuilder; import com.alibaba.citrus.util.ToStringBuilder.MapBuilder; import org.dom4j.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.xml.BeanDefinitionDecorator; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.NamespaceHandler; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; import org.springframework.util.ClassUtils; /** * 代表一个configuration point的实现,并处理configuration point * namespace下的elements及attributes。 * * @author Michael Zhou */ public class ConfigurationPointImpl extends NamespaceHandlerSupport implements ConfigurationPoint, NamespaceHandler, ConfigurationPointSourceInfo { private final static Logger log = LoggerFactory.getLogger(ConfigurationPoint.class); private final ConfigurationPoints cps; private final ConfigurationPointSettings settings; private final String name; private final String namespaceUri; private final String defaultElementName; private final String preferredNsPrefix; private final String contributionLocationPrefix; private final Map<ContributionKey, Contribution> contributions; private final Map<String, Contribution> dependingContributions; private final Collection<Contribution> dependingContributionsUnmodifiable; private final SourceInfo<SourceInfo<?>> sourceInfo; private VersionableSchemas schemas; private boolean initialized; ConfigurationPointImpl(ConfigurationPoints cps, ConfigurationPointSettings settings, String name, String namespaceUri, String defaultElementName, String preferredNsPrefix, SourceInfo<SourceInfo<?>> sourceInfo) { this.cps = cps; this.settings = settings; this.name = assertNotNull(name, "name"); this.namespaceUri = assertNotNull(namespaceUri, "namespaceUri"); this.defaultElementName = trimToNull(defaultElementName); this.preferredNsPrefix = trimToNull(preferredNsPrefix); this.contributionLocationPrefix = settings.baseLocation + name.replace('/', '-'); // eg. my-conf-point this.contributions = createTreeMap(); this.dependingContributions = createTreeMap(); // 使用排序的map,使测试结果恒定 this.dependingContributionsUnmodifiable = unmodifiableCollection(dependingContributions.values()); this.sourceInfo = assertNotNull(sourceInfo, "sourceInfo"); } public ConfigurationPoints getConfigurationPoints() { return cps; } public String getName() { return name; } public String getNamespaceUri() { return namespaceUri; } public String getDefaultElementName() { return defaultElementName; } public String getPreferredNsPrefix() { return preferredNsPrefix; } public NamespaceHandler getNamespaceHandler() { return this; } public Contribution getContribution(String name, ContributionType type) { return contributions.get(new ContributionKey(name, type)); } public Collection<Contribution> getContributions() { return contributions.values(); } @Override public Collection<Contribution> getDependingContributions() { return dependingContributionsUnmodifiable; } public void addDependingContribution(Contribution contribution) { if (contribution != null) { String key = contribution.getConfigurationPoint().getName() + "." + contribution.getName(); dependingContributions.put(key, contribution); } } public void init() { if (initialized) { return; } initialized = true; for (ContributionType type : ContributionType.values()) { loadContributions(type); } // 注册default element parser String defaultName = getDefaultElementName(); if (defaultName != null) { registerBeanDefinitionParser(defaultName, new DefaultElementDefinitionParser()); } } private void loadContributions(final ContributionType contribType) { final String contribLocation = contributionLocationPrefix + contribType.getContributionsLocationSuffix(); log.trace("Trying to load contributions at {}", contribLocation); final Map<String, String> sortedMappings = createTreeMap(); settings.resourceResolver.loadAllProperties(contribLocation, new PropertyHandler() { public void handle(String key, String value, Resource source, int lineNumber) { String contribName = trimToNull(key); String contribClassName = trimToNull(value); if (getDefaultElementName() != null && isEquals(contribName, getDefaultElementName())) { throw new FatalBeanException( "Contribution has a same name as the default element name for configuration point: contributionType=" + contribType + ", contribuitionClass=" + contribClassName + ", contributionName=" + contribName + ", configurationPoint=" + getName() + ", namespaceUri=" + getNamespaceUri()); } sortedMappings.put(contribName, contribClassName); ContributionImpl contrib = new ContributionImpl( ConfigurationPointImpl.this, settings, contribType, contribName, contribClassName, new SourceInfoSupport<ConfigurationPointSourceInfo>(ConfigurationPointImpl.this).setSource(source, lineNumber)); Contribution existContrib = contributions.get(contrib.getKey()); if (existContrib != null) { throw new ConfigurationPointException("Duplicated contributions from locations: " + contribLocation + "\n" + " " + existContrib + "\n and " + contrib); } register(contrib); contributions.put(contrib.getKey(), contrib); } }); if (log.isDebugEnabled() && !sortedMappings.isEmpty()) { ToStringBuilder buf = new ToStringBuilder(); buf.format("Loaded contributions at %s", contribLocation); buf.appendMap(sortedMappings); log.debug(buf.toString()); } } private void register(Contribution contrib) { // 当classLoader为null(IDE plugin)时,不创建和注册contribution class。 if (settings.classLoader == null) { return; } Object obj; try { obj = instantiateContributionImplementation(contrib); } catch (FatalBeanException e) { log.warn("Skipped registration of {} due to the error: {}", contrib.getDescription(), e); return; } if (obj instanceof ContributionAware) { ((ContributionAware) obj).setContribution(contrib); } switch (contrib.getType()) { case BEAN_DEFINITION_PARSER: registerBeanDefinitionParser(contrib.getName(), (BeanDefinitionParser) obj); break; case BEAN_DEFINITION_DECORATOR: registerBeanDefinitionDecorator(contrib.getName(), (BeanDefinitionDecorator) obj); break; case BEAN_DEFINITION_DECORATOR_FOR_ATTRIBUTE: registerBeanDefinitionDecoratorForAttribute(contrib.getName(), (BeanDefinitionDecorator) obj); break; default: unreachableCode("unknown contributionType: %s", contrib.getType()); } } private Object instantiateContributionImplementation(Contribution contrib) throws FatalBeanException { String implementationClassName = contrib.getImplementationClassName(); if (implementationClassName == null) { throw new FatalBeanException("Contribution class not defined: contributionType=" + contrib.getType() + ", contributionName=" + contrib.getName() + ", configurationPoint=" + contrib.getConfigurationPoint().getName() + ", namespaceUri=" + contrib.getConfigurationPoint().getNamespaceUri()); } Class<?> implementationClass; try { implementationClass = ClassUtils.forName(implementationClassName, settings.classLoader); } catch (ClassNotFoundException e) { throw new FatalBeanException("Contribution class not found: contributionType=" + contrib.getType() + ", contribuitionClass=" + implementationClassName + ", contributionName=" + contrib.getName() + ", configurationPoint=" + contrib.getConfigurationPoint().getName() + ", namespaceUri=" + contrib.getConfigurationPoint().getNamespaceUri(), e); } if (!contrib.getType().getContributionInterface().isAssignableFrom(implementationClass)) { throw new FatalBeanException("Contribution class does not implement the " + contrib.getType().getContributionInterface().getSimpleName() + " interface: contributionType=" + contrib.getType() + ", contribuitionClass=" + implementationClassName + ", contributionName=" + contrib.getName() + ", configurationPoint=" + contrib.getConfigurationPoint().getName() + ", namespaceUri=" + contrib.getConfigurationPoint().getNamespaceUri()); } return BeanUtils.instantiateClass(implementationClass); } public VersionableSchemas getSchemas() { if (schemas == null) { init(); // if not inited yet // eg. my-conf-point String mainName = getName().replace('/', '-'); Schema mainSchema = loadMainSchema(mainName); Schema[] versionedSchemas = loadVersionedSchemas(mainName); schemas = new VersionableSchemasImpl(mainSchema, versionedSchemas); } return schemas; } private Schema loadMainSchema(String mainName) { String schemaName = mainName + "." + XML_SCHEMA_EXTENSION; Document schemaSource = createConfigurationPointSchema(this, null); return SchemaImpl.createForConfigurationPoint( schemaName, null, namespaceUri, preferredNsPrefix, getDescription(), schemaSource, new SourceInfoSupport<ConfigurationPointSourceInfo>(this)); } private Schema[] loadVersionedSchemas(String mainName) { // 收集所有contribution schema的版本 Set<String> allVersions = createTreeSet(); for (Contribution contrib : getContributions()) { Collections.addAll(allVersions, contrib.getSchemas().getVersions()); } // 生成每个schemas。 Schema[] schemas = new Schema[allVersions.size()]; int i = 0; for (String version : allVersions) { String schemaName = mainName + "-" + version + "." + XML_SCHEMA_EXTENSION; Document schemaSource = createConfigurationPointSchema(this, version); schemas[i++] = SchemaImpl.createForConfigurationPoint( schemaName, version, namespaceUri, preferredNsPrefix, getDescription(), schemaSource, new SourceInfoSupport<ConfigurationPointSourceInfo>(this)); } return schemas; } public String getDescription() { return String.format("ConfigurationPoint[%s]", name); } public SourceInfo<?> getParent() { return sourceInfo.getParent(); } public Resource getSource() { return sourceInfo.getSource(); } public int getLineNumber() { return sourceInfo.getLineNumber(); } @Override public String toString() { ToStringBuilder buf = new ToStringBuilder(); buf.format("ConfigurationPoint[%s=%s, loaded contributions from %s.*]", name, namespaceUri, contributionLocationPrefix); MapBuilder mb = new MapBuilder(); if (!contributions.isEmpty()) { mb.append("Contributions", contributions.values()); } mb.append("Schemas", getSchemas()).appendTo(buf); return buf.toString(); } }