/*
* 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.support.SchemaUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static com.alibaba.citrus.util.io.StreamUtil.*;
import static java.util.Collections.*;
import static javax.xml.XMLConstants.*;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.alibaba.citrus.springext.ConfigurationPointException;
import com.alibaba.citrus.springext.ResourceResolver;
import com.alibaba.citrus.springext.ResourceResolver.Resource;
import com.alibaba.citrus.springext.Schema;
import com.alibaba.citrus.springext.SourceInfo;
import com.alibaba.citrus.springext.support.ConfigurationPointSchemaSourceInfo;
import com.alibaba.citrus.springext.support.ConfigurationPointSourceInfo;
import com.alibaba.citrus.springext.support.ContributionSchemaSourceInfo;
import com.alibaba.citrus.springext.support.ContributionSourceInfo;
import com.alibaba.citrus.springext.support.SchemaUtil;
import com.alibaba.citrus.springext.support.SourceInfoSupport;
import com.alibaba.citrus.springext.support.SpringPluggableSchemaSourceInfo;
import com.alibaba.citrus.springext.support.SpringSchemasSourceInfo;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.springframework.core.io.InputStreamSource;
public class SchemaImpl<P extends SourceInfo<?>> extends SchemaBase {
private final String name;
private final String version;
private final String sourceDesc;
private String targetNamespace;
private String preferredNsPrefix;
private String[] includes;
private final Map<String, Element> elements;
private final Collection<Element> elementCollection;
private final boolean parsingTargetNamespace;
private final SourceInfo<P> sourceInfo;
/** 创建 configuration point 的 main schema 和 versioned schema。 */
public static Schema createForConfigurationPoint(String name, String version, String targetNamespace, String preferredNsPrefix, String sourceDesc, Document sourceDocument, SourceInfo<ConfigurationPointSourceInfo> sourceInfo) {
class ConfigurationPointSchemaImpl extends SchemaImpl<ConfigurationPointSourceInfo>
implements ConfigurationPointSchemaSourceInfo {
private ConfigurationPointSchemaImpl(String name, String version, String targetNamespace, String preferredNsPrefix,
boolean parsingTargetNamespace, String sourceDesc, InputStreamSource source,
Document sourceDocument, boolean isInputStreamSource, SourceInfo<ConfigurationPointSourceInfo> sourceInfo) {
super(name, version, targetNamespace, preferredNsPrefix,
parsingTargetNamespace, sourceDesc, source,
sourceDocument, isInputStreamSource, sourceInfo);
}
}
return new ConfigurationPointSchemaImpl(name, version, targetNamespace, preferredNsPrefix, false, sourceDesc, null, sourceDocument, false, sourceInfo);
}
/** 创建 contribution 的 main schema 和 versioned schema。 */
public static Schema createForContribution(String name, String version, String sourceDesc, InputStreamSource source, SourceInfo<ContributionSourceInfo> sourceInfo, Transformer transformer, ResourceResolver resourceResolver) {
class ContributionSchemaImpl extends SchemaImpl<ContributionSourceInfo>
implements ContributionSchemaSourceInfo {
private ContributionSchemaImpl(String name, String version, String targetNamespace, String preferredNsPrefix,
boolean parsingTargetNamespace, String sourceDesc, InputStreamSource source,
Document sourceDocument, boolean isInputStreamSource, SourceInfo<ContributionSourceInfo> sourceInfo) {
super(name, version, targetNamespace, preferredNsPrefix,
parsingTargetNamespace, sourceDesc, source,
sourceDocument, isInputStreamSource, sourceInfo);
}
}
SchemaImpl schema = new ContributionSchemaImpl(name, version, null, null, false, sourceDesc, source, null, true, sourceInfo);
if (transformer != null) {
schema.transform(transformer); // 必须延迟处理(doNow == false),否则会死循环
}
// 强制转换成unqualified style
schema.transform(getUnqualifiedStyleTransformer(resourceResolver));
return schema;
}
/** 创建spring.schemas中定义的schema。 */
public static Schema createSpringPluggableSchema(String name, String version, boolean parsingTargetNamespace, String sourceDesc,
InputStreamSource source, SourceInfo<SpringSchemasSourceInfo> sourceInfo,
final Map<String, Map<String, String>> toolingParameters) {
class SpringPluggableSchemaImpl extends SchemaImpl<SpringSchemasSourceInfo>
implements SpringPluggableSchemaSourceInfo {
private SpringPluggableSchemaImpl(String name, String version, String targetNamespace, String preferredNsPrefix,
boolean parsingTargetNamespace, String sourceDesc, InputStreamSource source,
Document sourceDocument, boolean isInputStreamSource, SourceInfo<SpringSchemasSourceInfo> sourceInfo) {
super(name, version, targetNamespace, preferredNsPrefix,
parsingTargetNamespace, sourceDesc, source,
sourceDocument, isInputStreamSource, sourceInfo);
}
@Override
protected void doAnalyze() {
super.doAnalyze();
// 从spring.tooling参数表中获取preferredNsPrefix
String namespace = getTargetNamespace();
if (toolingParameters != null && namespace != null && toolingParameters.containsKey(namespace)) {
Map<String, String> params = toolingParameters.get(getTargetNamespace());
String preferredNsPrefix = trimToNull(params.get("prefix"));
if (preferredNsPrefix != null) {
setPreferredNsPrefix(preferredNsPrefix);
}
}
}
}
return new SpringPluggableSchemaImpl(name, version, null, null, parsingTargetNamespace, sourceDesc, source, null, true, sourceInfo);
}
/** 创建一般的schema。 */
public static Schema create(String name, String version, boolean parsingTargetNamespace, String sourceDesc, InputStreamSource source) {
return new SchemaImpl<SourceInfo<?>>(name, version, null, null, parsingTargetNamespace, sourceDesc, source, null, true, new SourceInfoSupport<SourceInfo<?>>());
}
/**
* 创建spring.schemas中定义的schema。
* <p>
* 如果<code>parsingTargetNamespace</code>为 <code>true</code>,则试图通过解析xml来取得ns。
* </p>
*/
private SchemaImpl(String name, String version, String targetNamespace, String preferredNsPrefix,
boolean parsingTargetNamespace, String sourceDesc,
InputStreamSource source, Document sourceDocument, boolean isInputStreamSource,
SourceInfo<P> sourceInfo) {
super(source, sourceDocument, isInputStreamSource);
this.name = name;
this.version = version;
this.targetNamespace = trimToNull(targetNamespace);
this.preferredNsPrefix = trimToNull(preferredNsPrefix);
this.parsingTargetNamespace = parsingTargetNamespace;
this.elements = createTreeMap();
this.elementCollection = unmodifiableCollection(elements.values());
this.sourceDesc = sourceDesc;
this.sourceInfo = assertNotNull(sourceInfo, "sourceInfo");
}
public String getName() {
return name;
}
public String getVersion() {
return version;
}
public String getTargetNamespace() {
if (parsingTargetNamespace) {
analyze();
}
return targetNamespace;
}
public String getPreferredNsPrefix() {
return preferredNsPrefix;
}
protected void setPreferredNsPrefix(String preferredNsPrefix) {
this.preferredNsPrefix = trimToNull(preferredNsPrefix);
}
public String[] getIncludes() {
analyze();
return includes == null ? EMPTY_STRING_ARRAY : includes;
}
public Collection<Element> getElements() {
analyze();
return elementCollection;
}
@Override
public Element getElement(String elementName) {
if (elementName != null) {
analyze();
return elements.get(elementName);
} else {
return null;
}
}
/** 由schemaSet来设置。 */
public void setElements(Collection<Element> elements) {
this.elements.clear();
if (elements != null) {
for (Element element : elements) {
this.elements.put(element.getName(), element);
}
}
}
public String getNamespacePrefix() {
return SchemaUtil.getNamespacePrefix(getPreferredNsPrefix(), getTargetNamespace());
}
public String getSourceDescription() {
return sourceDesc;
}
public String getText() {
return getText(null, null);
}
public String getText(String charset) {
return getText(charset, null);
}
public String getText(String charset, Transformer transformer) {
String content;
// parse
Document doc = getDocument();
// read text if it's an invalid XML doc.
if (doc == null) {
try {
return readText(getInputStream(), "ISO-8859-1", true);
} catch (IOException e) {
return null; // 不会发生
}
}
// filter the doc
if (transformer != null) {
doc = (Document) doc.clone(); // 避免修改schema中的document对象
transformer.transform(doc, getName());
}
// output
try {
content = SchemaUtil.getDocumentText(doc, charset);
} catch (Exception e) {
throw new ConfigurationPointException("Failed to read text of schema file: " + name + ", source=" + super.toString(), e);
}
return content;
}
/**
* 解析schema,取得以下信息:
* <ol>
* <li>targetNamespace</li>
* <li>include name</li>
* </ol>
*/
@Override
protected void doAnalyze() {
Document doc = getDocument(); // 不可能是null
org.dom4j.Element root = doc.getRootElement();
// return if not a schema file
if (!W3C_XML_SCHEMA_NS_URI.equals(root.getNamespaceURI()) || !"schema".equals(root.getName())) {
return;
}
// parse targetNamespace
if (parsingTargetNamespace) {
Attribute attr = root.attribute("targetNamespace");
if (attr != null) {
targetNamespace = trimToNull(attr.getStringValue());
}
}
// parse include
Namespace xsd = DocumentHelper.createNamespace("xsd", W3C_XML_SCHEMA_NS_URI);
QName includeName = DocumentHelper.createQName("include", xsd);
List<String> includeNames = createLinkedList();
// for each <xsd:include>
for (Iterator<?> i = root.elementIterator(includeName); i.hasNext(); ) {
org.dom4j.Element includeElement = (org.dom4j.Element) i.next();
String schemaLocation = trimToNull(includeElement.attributeValue("schemaLocation"));
if (schemaLocation != null) {
includeNames.add(schemaLocation);
}
}
includes = includeNames.toArray(new String[includeNames.size()]);
// parse xsd:element
QName elementName = DocumentHelper.createQName("element", xsd);
// for each <xsd:element>
for (Iterator<?> i = root.elementIterator(elementName); i.hasNext(); ) {
Element element = new ElementImpl((org.dom4j.Element) i.next());
if (element.getName() != null) {
this.elements.put(element.getName(), element);
}
}
}
public P getParent() {
return sourceInfo.getParent();
}
public Resource getSource() {
return sourceInfo.getSource();
}
public int getLineNumber() {
return sourceInfo.getLineNumber();
}
@Override
public String toString() {
if (targetNamespace == null) {
return String.format("Schema[name=%s, version=%s, source=%s]", name, version, super.toString());
} else {
return String.format("Schema[name=%s, version=%s, targetNamespace=%s, source=%s]", name, version,
targetNamespace, super.toString());
}
}
private static class ElementImpl implements Element {
private final static Namespace xsd = DocumentHelper.createNamespace("xsd", W3C_XML_SCHEMA_NS_URI);
private final static QName annotationName = DocumentHelper.createQName("annotation", xsd);
private final static QName documentationName = DocumentHelper.createQName("documentation", xsd);
private final String name;
private final String annotation;
private ElementImpl(org.dom4j.Element elementElement) {
// name
String name = trimToNull(elementElement.attributeValue("name"));
// annotation/documentation
org.dom4j.Element annotationElement = elementElement.element(annotationName);
org.dom4j.Element documentationElement = annotationElement == null ? null : annotationElement.element(documentationName);
String documentation = documentationElement == null ? null : documentationElement.getText();
if (documentation != null) {
documentation = trimToNull(documentation.replaceAll("[ \\t]*[\\r|\\n|\\r\\n][ \\t]*", "\n")); // 除去每行首尾空白,保留换行符
}
this.name = name;
this.annotation = documentation;
}
@Override
public String getName() {
return name;
}
@Override
public String getAnnotation() {
return annotation;
}
@Override
public String toString() {
return "Element[" + name + "]";
}
}
}