/* * 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.util.Assert.*; import static com.alibaba.citrus.util.CollectionUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import static java.util.Collections.*; import java.net.URI; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.alibaba.citrus.springext.Namespaces; import com.alibaba.citrus.springext.ResourceResolver; 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.Schemas; import com.alibaba.citrus.springext.SourceInfo; import com.alibaba.citrus.springext.support.ClasspathResourceResolver; import com.alibaba.citrus.springext.support.SourceInfoSupport; import com.alibaba.citrus.springext.support.SpringSchemasSourceInfo; import com.alibaba.citrus.util.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver; import org.springframework.beans.factory.xml.PluggableSchemaResolver; import org.springframework.util.ClassUtils; /** * 将Spring所支持的<code>META-INF/spring.schemas</code>中定义的schemas移到本地服务器。 * * @author Michael Zhou */ public class SpringPluggableSchemas implements Schemas, Namespaces { private final static Logger log = LoggerFactory.getLogger(SpringPluggableSchemas.class); private final static String SCHEMA_MAPPINGS_LOCATION = PluggableSchemaResolver.DEFAULT_SCHEMA_MAPPINGS_LOCATION; private final static String HANDLER_MAPPINGS_LOCATION = DefaultNamespaceHandlerResolver.DEFAULT_HANDLER_MAPPINGS_LOCATION; private final static String TOOLING_PARAMS_LOCATION = "META-INF/spring.tooling"; private final static Pattern SCHEMA_VERSION_PATTERN = Pattern.compile("-((\\d+)(\\.\\d+)*)\\.xsd$"); private final ResourceResolver resourceResolver; private final Map<String, Schema> nameToSchemaMappings; private final Map<String, String> uriToNameMappings; private final Set<String> namespaces; private final Set<String> namespacesUnmodified; private final Map<String, Map<String, String>> toolingParameters; private boolean initialized; /** 通过默认的<code>ClassLoader</code>来查找spring schemas。 */ public SpringPluggableSchemas() { this(null, null); } /** * 通过指定的<code>ClassLoader</code>来查找spring schemas。 * 如果未指定<code>ClassLoader</code>,则使用默认的<code>ClassLoader</code>。 */ public SpringPluggableSchemas(ClassLoader classLoader) { this(classLoader, null); } /** * 通过指定的<code>ResourceResolver</code>来查找spring schemas。 * 适合来实现IDE plugins。 */ public SpringPluggableSchemas(ResourceResolver resourceResolver) { this(null, assertNotNull(resourceResolver, "no resourceResolver was specified")); } private SpringPluggableSchemas(ClassLoader classLoader, ResourceResolver resourceResolver) { if (resourceResolver == null) { if (classLoader == null) { classLoader = ClassUtils.getDefaultClassLoader(); } this.resourceResolver = new ClasspathResourceResolver(classLoader); } else { // IDE plugin mode this.resourceResolver = resourceResolver; } this.nameToSchemaMappings = createTreeMap(); this.uriToNameMappings = createTreeMap(); this.namespaces = createTreeSet(); this.namespacesUnmodified = unmodifiableSet(namespaces); this.toolingParameters = createHashMap(); } @Override public Set<String> getAvailableNamespaces() { ensureInit(); return namespacesUnmodified; } public Map<String, Schema> getNamedMappings() { ensureInit(); return nameToSchemaMappings; } public Map<String, String> getUriToNameMappings() { ensureInit(); return uriToNameMappings; } public Map<String, String> getToolingParameters(String namespaceURI) { ensureInit(); return toolingParameters.get(trimToNull(namespaceURI)); } private void ensureInit() { if (initialized) { return; } initialized = true; log.trace("Trying to load Spring schema mappings at {}", SCHEMA_MAPPINGS_LOCATION); final String desc = "SpringSchema[" + SCHEMA_MAPPINGS_LOCATION + "]"; class SpringSchemasSourceInfoImpl extends SourceInfoSupport<SourceInfo<?>> implements SpringSchemasSourceInfo { SpringSchemasSourceInfoImpl() { } } // spring.tooling resourceResolver.loadAllProperties(TOOLING_PARAMS_LOCATION, new PropertyHandler() { public void handle(String key, String value, Resource source, int lineNumber) { String namespaceAndParamName = trimToNull(key); if (namespaceAndParamName != null) { int index = namespaceAndParamName.indexOf("@"); String namespace = null; String paramName = null; if (index >= 0) { namespace = trimToNull(namespaceAndParamName.substring(0, index)); paramName = trimToNull(namespaceAndParamName.substring(index + 1)); } if (namespace != null && paramName != null) { Map<String, String> params = toolingParameters.get(namespace); if (params == null) { params = createHashMap(); toolingParameters.put(namespace, params); } params.put(paramName, trimToNull(value)); } } } }); // spring.schemas resourceResolver.loadAllProperties(SCHEMA_MAPPINGS_LOCATION, new PropertyHandler() { public void handle(String key, String value, Resource source, int lineNumber) { String uri = trimToNull(key); String classpathLocation = trimToNull(value); if (uri == null || classpathLocation == null) { return; // invalid and ignored } String schemaName = getSchemaName(uri); Matcher matcher = SCHEMA_VERSION_PATTERN.matcher(schemaName); String version = null; if (matcher.find()) { version = matcher.group(1); } SpringSchemasSourceInfoImpl pluginSourceInfo = new SpringSchemasSourceInfoImpl(); pluginSourceInfo.setSource(source, lineNumber); Resource schemaSource = getResource(classpathLocation, uri); if (schemaSource != null) { Schema schema = SchemaImpl.createSpringPluggableSchema( schemaName, version, true, desc, schemaSource, new SourceInfoSupport<SpringSchemasSourceInfo>(pluginSourceInfo).setSource(schemaSource), toolingParameters); nameToSchemaMappings.put(schemaName, schema); uriToNameMappings.put(uri, schemaName); String namespace = schema.getTargetNamespace(); if (namespace != null) { namespaces.add(namespace); } } } }); // 在spring.handlers中,有一些namespace是没有schema的(例如:http://www.springframework.org/schema/p),所以在spring.schemas中找不到。 // 但我们需要在这里把它引进来,以方便IDE plugins读取。 resourceResolver.loadAllProperties(HANDLER_MAPPINGS_LOCATION, new PropertyHandler() { public void handle(String key, String value, Resource source, int lineNumber) { String namespace = trimToNull(key); if (namespace != null) { namespaces.add(namespace); } } }); if (log.isDebugEnabled() && !uriToNameMappings.isEmpty()) { ToStringBuilder buf = new ToStringBuilder(); buf.format("Loaded Spring schema mappings at %s, %d schemas found.", SCHEMA_MAPPINGS_LOCATION, uriToNameMappings.size()).appendMap(uriToNameMappings); log.debug(buf.toString()); } } private Resource getResource(String classpathLocation, String uri) { Resource resource = resourceResolver.getResource(classpathLocation); if (resource == null) { log.warn("Could not find schema {} for URI: {}", classpathLocation, uri); } return resource; } private String getSchemaName(String uri) { // 替换URI,将http://www.springframework.org/schema/beans/spring-beans-2.5.xsd // 替换成:www.springframework.org/schema/beans/spring-beans-2.5.xsd return URI.create(uri).normalize().getSchemeSpecificPart().replaceAll("^/+|/+$", ""); } @Override public String toString() { ToStringBuilder buf = new ToStringBuilder(); buf.format("SpringPluggableSchemas[loaded from %s]", SCHEMA_MAPPINGS_LOCATION); buf.appendMap(uriToNameMappings); return buf.toString(); } }