/*
* (c) Copyright 2008-2011 by Volker Bergmann. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted under the terms of the
* GNU General Public License.
*
* For redistributing this software or a derivative work under a license other
* than the GPL-compatible Free Software License as defined by the Free
* Software Foundation or approved by OSI, you must first obtain a commercial
* license to this software product from Volker Bergmann.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
* REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
* HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.databene.platform.xml;
import java.io.Closeable;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.databene.script.expression.ExpressionUtil.*;
import org.databene.benerator.engine.BeneratorContext;
import org.databene.benerator.engine.ResourceManager;
import org.databene.benerator.engine.ResourceManagerSupport;
import org.databene.benerator.engine.parser.xml.BeanParser;
import org.databene.benerator.engine.parser.xml.BeneratorParseContext;
import org.databene.benerator.engine.parser.xml.IncludeParser;
import org.databene.benerator.engine.statement.BeanStatement;
import org.databene.benerator.engine.statement.IncludeStatement;
import org.databene.benerator.parser.ModelParser;
import org.databene.commons.Assert;
import org.databene.commons.BeanUtil;
import org.databene.commons.CollectionUtil;
import org.databene.commons.ConfigurationError;
import org.databene.commons.Context;
import org.databene.commons.StringUtil;
import org.databene.commons.context.ContextAware;
import org.databene.commons.xml.XMLUtil;
import org.databene.model.data.AlternativeGroupDescriptor;
import org.databene.model.data.ComplexTypeDescriptor;
import org.databene.model.data.ComponentDescriptor;
import org.databene.model.data.DataModel;
import org.databene.model.data.DefaultDescriptorProvider;
import org.databene.model.data.FeatureDescriptor;
import org.databene.model.data.InstanceDescriptor;
import org.databene.model.data.Mode;
import org.databene.model.data.PartDescriptor;
import org.databene.model.data.SimpleTypeDescriptor;
import org.databene.model.data.TypeDescriptor;
import org.databene.model.data.UnionSimpleTypeDescriptor;
import org.databene.model.data.UnresolvedTypeDescriptor;
import org.databene.script.Expression;
import org.databene.script.expression.ConstantExpression;
import org.databene.script.PrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import static org.databene.commons.xml.XMLUtil.*;
/**
* Parses an XML schema file into a benerator metadata structure.<br/>
* <br/>
* Created: 27.02.2008 09:40:45
* @since 0.5.0
* @author Volker Bergmann
*/
public class XMLSchemaDescriptorProvider extends DefaultDescriptorProvider implements ContextAware, ResourceManager {
private static Logger LOGGER = LoggerFactory.getLogger(XMLSchemaDescriptorProvider.class);
private static final String SCHEMA_NAMESPACE = "http://www.w3.org/2001/XMLSchema";
public static final String INCLUDE = "include";
public static final String IMPORT = "import";
public static final String SIMPLE_TYPE = "simpleType";
public static final String COMPLEX_TYPE = "complexType";
public static final String ANNOTATION = "annotation";
public static final String SEQUENCE = "sequence";
public static final String CHOICE = "choice";
public static final String EXTENSION = "extension";
public static final String UNION = "union";
public static final String ELEMENT = "element";
public static final String GROUP = "group";
public static final String ATTRIBUTE_GROUP = "attributeGroup";
public static final String NAME = "name";
public static final String RESTRICTION = "restriction";
public static final String BASE = "base";
public static final String VALUE = "value";
public static final String LENGTH = "length";
public static final String MIN_INCLUSIVE = "minInclusive";
public static final String MIN_EXCLUSIVE = "minExclusive";
public static final String MAX_EXCLUSIVE = "maxExclusive";
public static final String MAX_INCLUSIVE = "maxInclusive";
public static final String ENUMERATION = "enumeration";
private static final String TYPE = "type";
private static final String REF = "ref";
private static final String KEY = "key";
private static final String KEYREF = "keyref";
private static final String UNIQUE = "unique";
private static final String ALL = "all";
private static final String COMPLEX_CONTENT = "complexContent";
private static final String SIMPLE_CONTENT = "simpleContent";
private static final String ATTRIBUTE = "attribute";
// attributes ------------------------------------------------------------------------------------------------------
private BeneratorContext context;
private String schemaUri;
private ModelParser parser;
private Map<String, String> namespaces;
private ResourceManager resourceManager = new ResourceManagerSupport();
// constructors ----------------------------------------------------------------------------------------------------
public XMLSchemaDescriptorProvider(String schemaUri, BeneratorContext context) {
super(schemaUri, context.getDataModel(), true);
new XMLNativeTypeDescriptorProvider(SCHEMA_NAMESPACE, dataModel);
this.namespaces = new HashMap<String, String>();
parser = new ModelParser(context);
setContext(context);
setSchemaUri(schemaUri);
}
// interface -------------------------------------------------------------------------------------------------------
public void setSchemaUri(String schemaUri) {
this.schemaUri = schemaUri;
checkSchema();
}
public void setContext(Context context) {
this.context = (BeneratorContext) context;
checkSchema();
}
public BeneratorContext getContext() {
return context;
}
// ResourceManager interface implementation ------------------------------------------------------------------------
public boolean addResource(Closeable resource) {
return resourceManager.addResource(resource);
}
public void close() {
resourceManager.close();
}
// private helpers -------------------------------------------------------------------------------------------------
private void checkSchema() {
if (!StringUtil.isEmpty(schemaUri) && context != null) {
try {
Document document = parse(schemaUri);
this.namespaces = getNamespaces(document);
this.id = getTargetNamespace(document);
dataModel.addDescriptorProvider(this);
parseStructure(document);
parseDetails(document);
} catch (IOException e) {
throw new ConfigurationError("Error parsing schemaUri: " + schemaUri, e);
}
}
}
private void parseStructure(Document document) throws IOException {
LOGGER.debug("parseStructure()");
Element root = document.getDocumentElement();
Element[] childElements = XMLUtil.getChildElements(root);
for (Element element : childElements) {
String nodeName = localName(element);
String nameAttribute = element.getAttribute("name");
Set<String> COMPLEX_ELEMENTS = CollectionUtil.toSet(COMPLEX_TYPE, GROUP, ATTRIBUTE_GROUP);
if (COMPLEX_ELEMENTS.contains(nodeName))
addTypeDescriptor(new ComplexTypeDescriptor(nameAttribute, this));
else if (SIMPLE_TYPE.equals(nodeName))
addTypeDescriptor(new SimpleTypeDescriptor(nameAttribute, this));
else if (ELEMENT.equals(nodeName)) {
String typeName = element.getAttribute("type");
if (!StringUtil.isEmpty(typeName)) {
TypeDescriptor elementType = dataModel.getTypeDescriptor(typeName);
if (elementType instanceof SimpleTypeDescriptor)
addTypeDescriptor(new SimpleTypeDescriptor(nameAttribute, this));
else if (elementType instanceof SimpleTypeDescriptor)
addTypeDescriptor(new ComplexTypeDescriptor(nameAttribute, this));
else
addTypeDescriptor(new UnresolvedTypeDescriptor(nameAttribute, this, typeName));
} else if (XMLUtil.getChildElements(element, false, "complexType").length > 0) {
addTypeDescriptor(new ComplexTypeDescriptor(nameAttribute, this));
} else if (XMLUtil.getChildElements(element, false, SIMPLE_TYPE).length > 0) {
addTypeDescriptor(new SimpleTypeDescriptor(nameAttribute, this));
} else
addTypeDescriptor(new ComplexTypeDescriptor(nameAttribute, this));
} else if (ANNOTATION.equals(nodeName))
parseDocumentAnnotation(element);
else if (IMPORT.equals(nodeName))
parseImport(element);
else if (INCLUDE.equals(nodeName))
parseStructureOfInclude(element);
}
resolveTypes();
}
private void resolveTypes() {
boolean unresolved = false;
do {
for (TypeDescriptor type : typeMap.values())
if (type instanceof UnresolvedTypeDescriptor) {
TypeDescriptor parent = type.getParent();
if (parent instanceof SimpleTypeDescriptor)
addTypeDescriptor(new SimpleTypeDescriptor(type.getName(), this, type.getParentName()));
else if (parent instanceof ComplexTypeDescriptor)
addTypeDescriptor(new ComplexTypeDescriptor(type.getName(), this, type.getParentName()));
else if (parent == null)
throw new ConfigurationError("parentType " + type.getParentName() + " not found for " + type.getName());
else
unresolved = true;
}
} while (unresolved);
}
private void parseDetails(Document document) throws IOException {
LOGGER.debug("parseDetails()");
Element root = document.getDocumentElement();
Element[] childElements = XMLUtil.getChildElements(root);
for (Element element : childElements) {
String nodeName = localName(element);
if (ELEMENT.equals(nodeName))
parseTopLevelElement(element);
else if (COMPLEX_TYPE.equals(nodeName))
parseComplexType(element, null, null, true);
else if (SIMPLE_TYPE.equals(nodeName))
addTypeDescriptor(parseSimpleType(null, element));
else if (GROUP.equals(nodeName))
parseGroup(element);
else if (ATTRIBUTE_GROUP.equals(nodeName))
parseAttributeGroup(element);
else if (IMPORT.equals(nodeName))
parseImport(element);
else if (INCLUDE.equals(nodeName))
parseDetailsOfInclude(element);
else if (!ANNOTATION.equals(nodeName))
throw unsupportedElementType(element, root);
}
}
private void parseDocumentAnnotation(Element element) {
Annotation annotation = new Annotation(element);
Element appInfo = annotation.getAppInfo();
if (appInfo == null)
return;
for (Element child : XMLUtil.getChildElements(appInfo)) {
String childName = XMLUtil.localName(child);
if (INCLUDE.equals(childName)) {
IncludeStatement statement = (IncludeStatement) new IncludeParser().parse(child, null, new BeneratorParseContext(this));
statement.execute(context);
} else if ("bean".equals(childName)) {
Expression<?> beanExpression = BeanParser.parseBeanExpression(child);
String id = child.getAttribute("id");
new BeanStatement(id, beanExpression, this).execute(context);
beanExpression.evaluate(context);
} else
throw new UnsupportedOperationException("Document annotation type not supported: "
+ child.getNodeName());
}
}
private ComplexTypeDescriptor parseComplexType(Element complexTypeElement, String parentName, Annotation annotationBefore, boolean global) {
String name = (parentName != null ? parentName : complexTypeElement.getAttribute(NAME));
LOGGER.debug("parseComplexType({})", name);
if (name == null)
throw new ConfigurationError("unnamed complex type");
ComplexTypeDescriptor descriptor = new ComplexTypeDescriptor(name, this);
if (annotationBefore != null)
descriptor = parseElementAppInfo(descriptor, annotationBefore);
Annotation annotation = null;
Element[] children = XMLUtil.getChildElements(complexTypeElement);
for (Element child : children) {
String nodeName = localName(child);
if ("annotation".equals(nodeName))
annotation = new Annotation(child);
else if (SEQUENCE.equals(nodeName))
parseSequence(child, descriptor);
else if (COMPLEX_CONTENT.equals(nodeName))
parseComplexContent(child, descriptor);
else if (ALL.equals(nodeName))
parseAll(child, descriptor);
else if (SIMPLE_CONTENT.equals(nodeName))
parseSimpleContent(child, descriptor);
else if (ATTRIBUTE.equals(nodeName)) {
parseAttribute(child, descriptor);
} else if (ATTRIBUTE_GROUP.equals(nodeName)) {
ComplexTypeDescriptor group = parseAttributeGroup(child);
for (InstanceDescriptor component : group.getParts())
descriptor.addPart(component);
} else
throw unsupportedElementType(child, complexTypeElement);
}
descriptor = parseComplexTypeAppinfo(descriptor, annotation);
if (global)
addTypeDescriptor(descriptor);
return descriptor;
}
private ComplexTypeDescriptor parseComplexTypeAppinfo(
ComplexTypeDescriptor descriptor, Annotation annotation) {
if (annotation == null || annotation.getAppInfo() == null)
return descriptor;
Element appInfo = annotation.getAppInfo();
Element[] infos = XMLUtil.getChildElements(appInfo);
if (infos.length > 1)
throw new ConfigurationError("Cannot handle more than one appinfo in a complex type");
Element info = infos[0];
parser.parseComplexTypeChild(info, descriptor);
return descriptor;
}
private void parseComplexContent(Element complexContent, ComplexTypeDescriptor owner) {
Element[] children = XMLUtil.getChildElements(complexContent);
for (Element child : children) {
String nodeName = localName(child);
if (EXTENSION.equals(nodeName))
parseExtension(child, owner);
else if (RESTRICTION.equals(nodeName))
parseComplexRestriction(child, owner);
else
throw unsupportedElementType(child, complexContent);
}
}
private void parseComplexRestriction(Element restrictionElement, ComplexTypeDescriptor owner) {
// TODO v0.8 test this
Element[] children = XMLUtil.getChildElements(restrictionElement);
for (Element child : children) {
String nodeName = localName(child);
if (ATTRIBUTE.equals(nodeName))
parseAttribute(child, owner);
else
throw unsupportedElementType(child, restrictionElement);
}
}
private ComplexTypeDescriptor parseSimpleContent(Element simpleContentElement, ComplexTypeDescriptor complexType) {
Annotation annotation = null;
LOGGER.debug("parseSimpleContent()");
for (Element child : XMLUtil.getChildElements(simpleContentElement)) {
String localName = localName(child);
if (ANNOTATION.equals(localName))
annotation = new Annotation(child);
else if (RESTRICTION.equals(localName))
parseSimpleContentRestriction(child, complexType);
else if (EXTENSION.equals(localName))
parseSimpleContentExtension(child, complexType);
else
throw unsupportedElementType(child, simpleContentElement);
}
if (annotation != null && annotation.getAppInfo() != null)
complexType = parseComplexTypeAppinfo(complexType, annotation);
return complexType;
}
private void parseSimpleContentRestriction(Element restriction, ComplexTypeDescriptor complexType) {
String baseName = restriction.getAttribute("base");
Assert.notNull(baseName, "base attribute");
TypeDescriptor base = dataModel.getTypeDescriptor(baseName);
Assert.notNull(base, "base type");
if (!(base instanceof ComplexTypeDescriptor))
throw new ConfigurationError("Expected ComplexTypeDescriptor for " + baseName + ", found: " +
base.getClass().getSimpleName());
complexType.setParent(base);
SimpleTypeDescriptor content = (SimpleTypeDescriptor) complexType.getComponent(ComplexTypeDescriptor.__SIMPLE_CONTENT).getLocalType(false);
Assert.notNull(content, "content");
parseRestrictionChildren(restriction, content);
}
private void parseSimpleContentExtension(Element extension, ComplexTypeDescriptor complexType) {
String baseName = extension.getAttribute("base");
Assert.notNull(baseName, "base attribute");
TypeDescriptor base = dataModel.getTypeDescriptor(baseName);
Assert.notNull(base, "base type");
if (base instanceof SimpleTypeDescriptor) {
complexType.addComponent(new PartDescriptor(ComplexTypeDescriptor.__SIMPLE_CONTENT, this, baseName, null,
new ConstantExpression<Long>(1L), new ConstantExpression<Long>(1L)));
} else if (base instanceof ComplexTypeDescriptor)
complexType.setParentName(baseName);
else
throw new UnsupportedOperationException("not a supported type: " + base.getClass());
parseAttributes(extension, complexType);
}
private void parseExtension(Element extension, ComplexTypeDescriptor descriptor) {
String base = extension.getAttribute(BASE);
descriptor.setParentName(base);
parseAttributes(extension, descriptor);
}
private void parseAttributes(Element extension, ComplexTypeDescriptor owner) {
Element[] children = XMLUtil.getChildElements(extension);
for (Element child : children) {
String nodeName = localName(child);
if (ATTRIBUTE.equals(nodeName))
parseAttribute(child, owner);
else
throw unsupportedElementType(child, extension);
}
}
private TypeDescriptor parseTopLevelElement(Element element) {
String name = element.getAttribute(NAME);
LOGGER.debug("parseTopLevelElement({})", name);
TypeDescriptor descriptor = null;
Annotation annotation = null;
Element[] children = XMLUtil.getChildElements(element);
for (Element child : children) {
String nodeName = localName(child);
if (COMPLEX_TYPE.equals(nodeName)) {
descriptor = parseComplexType(child, name, annotation, false);
annotation = null;
} else if (SIMPLE_TYPE.equals(nodeName))
descriptor = parseSimpleType(name, child);
else if (KEY.equals(nodeName))
parseKey(child);
else if (KEYREF.equals(nodeName))
parseKeyRef(child);
else if (ANNOTATION .equals(nodeName))
annotation = new Annotation(child);
else
throw unsupportedElementType(child, element);
}
if (descriptor == null) {
String type = element.getAttribute("type");
if (!StringUtil.isEmpty(type))
descriptor = parseTopLevelElementWithType(element);
}
descriptor = parseElementAppInfo(descriptor, annotation);
if (descriptor == null)
descriptor = new ComplexTypeDescriptor(name, this);
addTypeDescriptor(descriptor);
return descriptor;
}
private ComponentDescriptor parseContainedElement(Element element, ComplexTypeDescriptor owner) {
String name = element.getAttribute(NAME);
LOGGER.debug("parseElement({})", element.getAttribute(NAME));
Assert.notNull(owner, "owner");
PartDescriptor descriptor = null;
if (!StringUtil.isEmpty(element.getAttribute(REF)))
descriptor = parseElementRef(element);
Annotation annotation = null;
Element[] children = XMLUtil.getChildElements(element);
for (Element child : children) {
String nodeName = localName(child);
if (COMPLEX_TYPE.equals(nodeName)) {
ComplexTypeDescriptor type = parseComplexType(child, name, annotation, false);
descriptor = new PartDescriptor(name, this, type);
annotation = null;
} else if (SIMPLE_TYPE.equals(nodeName)) {
SimpleTypeDescriptor simpleType = parseSimpleType(name, child);
ComplexTypeDescriptor complexType = wrapSimpleTypeWithComplexType(simpleType);
descriptor = new PartDescriptor(name, this, complexType);
} else if (KEY.equals(nodeName))
parseKey(child);
else if (KEYREF.equals(nodeName))
parseKeyRef(child);
else if (UNIQUE.equals(nodeName))
parseUnique(child);
else if (ANNOTATION .equals(nodeName))
annotation = new Annotation(child);
else
throw unsupportedElementType(child, element);
}
if (descriptor == null) {
String type = element.getAttribute("type");
if (!StringUtil.isEmpty(type)) {
descriptor = parseElementWithType(element);
}
} else
parseElementAppInfo(descriptor, annotation);
if (descriptor == null)
descriptor = new PartDescriptor(name, this, "string"); // possibly there i a more useful default type
parseOccurrences(element, descriptor);
if ("false".equals(element.getAttribute("nillable")))
descriptor.setNullable(false);
owner.addComponent(descriptor);
return descriptor;
}
private ComplexTypeDescriptor wrapSimpleTypeWithComplexType(SimpleTypeDescriptor simpleType) {
ComplexTypeDescriptor complexType = new ComplexTypeDescriptor(simpleType.getName(), this);
complexType.addComponent(new PartDescriptor(ComplexTypeDescriptor.__SIMPLE_CONTENT, this, simpleType));
return complexType;
}
private void parseUnique(Element child) {
// TODO v1.0 automatically support uniqueness
LOGGER.warn("<unique> is not supported. Please define own annotations or setup for uniqueness assurance");
}
private PartDescriptor parseElementRef(Element element) {
String refName = element.getAttribute(REF);
if (StringUtil.isEmpty(refName))
throw new ConfigurationError("no ref specified in element");
TypeDescriptor type = dataModel.getTypeDescriptor(refName);
PartDescriptor descriptor;
if (type instanceof SimpleTypeDescriptor) {
ComplexTypeDescriptor complexType = new ComplexTypeDescriptor(refName, this);
complexType.addComponent(new PartDescriptor(ComplexTypeDescriptor.__SIMPLE_CONTENT, this, refName));
descriptor = new PartDescriptor(refName, this, complexType);
} else {
descriptor = new PartDescriptor(refName, this, type);
}
return descriptor;
}
@SuppressWarnings("unchecked")
private <T extends FeatureDescriptor> T parseElementAppInfo(T descriptor, Annotation annotation) {
if (annotation == null || annotation.getAppInfo() == null)
return descriptor;
Element appInfo = annotation.getAppInfo();
Element[] infos = XMLUtil.getChildElements(appInfo);
for (Element info : infos) {
String childName = XMLUtil.localName(info);
if ("bean".equals(childName))
BeanParser.parseBeanExpression(info);
else if ("variable".equals(childName))
parser.parseVariable(info, (ComplexTypeDescriptor) descriptor);
else if (ATTRIBUTE.equals(childName))
descriptor = (T) parser.parseAttribute(info, null, (PartDescriptor) descriptor);
else if ("part".equals(childName))
descriptor = (T) parser.parsePart(info, null, (PartDescriptor) descriptor);
else if (descriptor instanceof ComplexTypeDescriptor)
descriptor = (T) parser.parseComplexType(info, (ComplexTypeDescriptor) descriptor);
else if (descriptor instanceof SimpleTypeDescriptor)
descriptor = (T) parser.parseSimpleType(info, (SimpleTypeDescriptor) descriptor);
else if ("type".equals(childName)) {
TypeDescriptor typeDescriptor = (descriptor instanceof InstanceDescriptor ? ((InstanceDescriptor) descriptor).getTypeDescriptor() : (TypeDescriptor) descriptor);
if (typeDescriptor instanceof SimpleTypeDescriptor)
descriptor = (T) parser.parseSimpleType(info, (SimpleTypeDescriptor) typeDescriptor);
else
descriptor = (T) parser.parseComplexType(info, (ComplexTypeDescriptor) typeDescriptor);
} else
throw new UnsupportedOperationException("Unsupported element (" + childName + ") " +
"or descriptor type: " + descriptor.getClass().getName());
}
return descriptor;
}
/**
* Parses code like
* <pre>
* <xs:element name="variable" type="generator-setup"/>
* </pre>
* @param element
*/
private TypeDescriptor parseTopLevelElementWithType(Element element) {
String name = element.getAttribute(NAME);
String typeName = element.getAttribute("type");
TypeDescriptor type = getType(typeName);
if (type == null)
type = getType(name);
if (type != null) {
if (type instanceof SimpleTypeDescriptor)
return new SimpleTypeDescriptor(name, this, typeName);
else if (type instanceof ComplexTypeDescriptor)
return new ComplexTypeDescriptor(name, this, typeName);
// else if (parentType instanceof UnresolvedTypeDescriptor)
// return new UnresolvedTypeDescriptor(name, typeName);
else
throw new UnsupportedOperationException("Unsupported descriptor: " + type);
} else
throw new UnsupportedOperationException("Unsupported type: " + typeName);
}
private PartDescriptor parseElementWithType(Element element) {
String name = element.getAttribute(NAME);
String typeName = element.getAttribute("type");
TypeDescriptor type = getType(typeName);
if (type == null)
throw new ConfigurationError("Undefined type: " + typeName);
PartDescriptor refDesc;
if (type instanceof SimpleTypeDescriptor) {
// the element wraps a simple type
SimpleTypeDescriptor localType = new SimpleTypeDescriptor(name, this, typeName);
ComplexTypeDescriptor contentType = wrapSimpleTypeWithComplexType(localType);
refDesc = new PartDescriptor(name, this, contentType);
Element anno = XMLUtil.getChildElement(element, false, false, "annotation");
if (anno != null)
parseSimpleTypeAppinfo(new Annotation(anno), localType);
} else {
ComplexTypeDescriptor localType = new ComplexTypeDescriptor(name, this, typeName);
refDesc = new PartDescriptor(name, this, localType);
Element anno = XMLUtil.getChildElement(element, false, false, "annotation");
if (anno != null)
refDesc = parseAttributeAppinfo(new Annotation(anno), refDesc);
}
parseOccurrences(element, refDesc);
return refDesc;
}
private TypeDescriptor getType(String typeName) {
int sep = typeName.indexOf(':');
if (sep < 0)
return dataModel.getTypeDescriptor(typeName);
String nsAlias = typeName.substring(0, sep);
String namespace = getNamespaceForAlias(nsAlias);
String typeInNs = typeName.substring(sep + 1);
TypeDescriptor type = dataModel.getTypeDescriptor(namespace, typeInNs);
return type;
}
private String getNamespaceForAlias(String nsAlias) {
return namespaces.get(nsAlias);
}
private void parseOccurrences(Element element, InstanceDescriptor descriptor) {
Long minOccurs = XMLUtil.getLongAttribute(element, "minOccurs", 1L);
String maxOccursString = element.getAttribute("maxOccurs");
Long maxOccurs = 1L;
if (!StringUtil.isEmpty(maxOccursString))
maxOccurs = ("unbounded".equals(maxOccursString) ? null : Long.parseLong(maxOccursString));
if (minOccurs.equals(maxOccurs) && descriptor.getCount() != null) {
descriptor.setCount(constant(maxOccurs));
descriptor.setMinCount(null);
descriptor.setMaxCount(null);
} else {
descriptor.setCount(null);
if (descriptor.getMinCount() == null)
descriptor.setMinCount(constant(minOccurs));
if (descriptor.getMaxCount() == null)
descriptor.setMaxCount(constant(maxOccurs));
}
}
private void parseKeyRef(Element child) {
// TODO v1.0 implement parseKeyRef
LOGGER.warn("KeyRefs are not supported, yet. Ignoring keyRef: " + child.getAttribute("name"));
}
private void parseKey(Element child) {
// TODO v1.0 implement parseKey
LOGGER.warn("Keys are not supported, yet. Ignoring key: " + child.getAttribute("name"));
}
@SuppressWarnings("null")
private ComponentDescriptor parseAttribute(Element attributeElement, ComplexTypeDescriptor owner) {
String name = attributeElement.getAttribute(NAME);
LOGGER.debug("parseAttribute({})", name);
if (StringUtil.isEmpty(name))
throw new ConfigurationError("Unnamed attribute");
Element[] children = XMLUtil.getChildElements(attributeElement);
String use = attributeElement.getAttribute("use");
Boolean nullable = ("required".equals(use) ? Boolean.FALSE : null);
Annotation annotation = null;
ComponentDescriptor descriptor = null;
for (Element child : children) {
String nodeName = localName(child);
if ("annotation".equals(nodeName))
annotation = new Annotation(child);
else if (SIMPLE_TYPE.equals(nodeName)) {
descriptor = new PartDescriptor(name, this, parseSimpleType(null, child));
} else
throw unsupportedElementType(child, attributeElement);
}
String type = attributeElement.getAttribute("type");
if (descriptor == null && type != null) {
descriptor = new PartDescriptor(name, this, type);
if (nullable != null && !nullable)
descriptor.setNullable(false);
}
if (annotation != null && annotation.getAppInfo() != null)
descriptor = parseAttributeAppinfo(annotation, descriptor);
String fixed = attributeElement.getAttribute("fixed");
if (!StringUtil.isEmpty(fixed))
((SimpleTypeDescriptor) descriptor.getLocalType(false)).setValues(fixed);
else {
String defaultValue = attributeElement.getAttribute("default");
if (!StringUtil.isEmpty(defaultValue))
((SimpleTypeDescriptor) descriptor.getLocalType(false)).setValues(defaultValue);
}
descriptor.setCount(new ConstantExpression<Long>(1L));
if ("prohibited".equals(attributeElement.getAttribute("use")))
descriptor.setMode(Mode.ignored);
owner.addComponent(descriptor);
return descriptor;
}
@SuppressWarnings("unchecked")
private <T extends ComponentDescriptor> T parseAttributeAppinfo(Annotation annotation, T descriptor) {
Element appInfo = annotation.getAppInfo();
if (appInfo == null)
return descriptor;
Element[] infos = XMLUtil.getChildElements(appInfo);
if (infos.length > 1)
throw new ConfigurationError("Cannot handle more than one appinfo in a simple type");
if (infos.length == 0)
return descriptor;
Element info = infos[0];
return (T) parser.parseSimpleTypeComponent(info, null, descriptor);
}
private SimpleTypeDescriptor parseSimpleType(String name, Element simpleType) {
Annotation annotation = null;
SimpleTypeDescriptor descriptor = null;
if (name == null)
name = simpleType.getAttribute("name");
LOGGER.debug("parseSimpleType({})", name);
for (Element child : XMLUtil.getChildElements(simpleType)) {
String localName = localName(child);
if (ANNOTATION.equals(localName)) {
annotation = new Annotation(child);
} else if (UNION.equals(localName)) {
descriptor = parseUnion(child, name);
} else if (RESTRICTION.equals(localName)) {
descriptor = parseSimpleTypeRestriction(child, name);
} else
throw unsupportedElementType(child, simpleType);
}
if (descriptor == null) {
String type = simpleType.getAttribute(TYPE);
descriptor = new SimpleTypeDescriptor(name, this, type);
}
if (annotation != null && annotation.getAppInfo() != null)
descriptor = parseSimpleTypeAppinfo(annotation, descriptor);
return descriptor;
}
private SimpleTypeDescriptor parseSimpleTypeAppinfo(
Annotation annotation, SimpleTypeDescriptor descriptor) {
Element appInfo = annotation.getAppInfo();
if (appInfo != null) {
Element[] infos = XMLUtil.getChildElements(appInfo);
if (infos.length > 1)
throw new ConfigurationError("Cannot handle more than one appinfo in a simple type");
parser.parseSimpleType(infos[0], descriptor);
}
return descriptor;
}
private SimpleTypeDescriptor parseUnion(Element union, String name) {
LOGGER.debug("parseUnion({})", name);
UnionSimpleTypeDescriptor descriptor = new UnionSimpleTypeDescriptor(name, this);
Element[] children = XMLUtil.getChildElements(union);
for (Element child : children) {
String nodeName = localName(child);
if (SIMPLE_TYPE.equals(nodeName)) {
descriptor.addAlternative(parseSimpleType(null, child));
} else
throw unsupportedElementType(child, union);
}
String memberTypes = union.getAttribute("memberTypes");
if (!StringUtil.isEmpty(memberTypes)) {
String[] tokens = StringUtil.tokenize(memberTypes, ' ');
for (String token : tokens)
if (!StringUtil.isEmpty(token))
descriptor.addAlternative(new SimpleTypeDescriptor("_local", this, token));
}
return descriptor;
}
private SimpleTypeDescriptor parseSimpleTypeRestriction(Element restriction, String name) {
String base = XMLUtil.localName(restriction.getAttribute(BASE));
SimpleTypeDescriptor descriptor = new SimpleTypeDescriptor(name, this, base);
parseRestrictionChildren(restriction, descriptor);
return descriptor;
}
private void parseRestrictionChildren(Element restriction,
SimpleTypeDescriptor descriptor) {
Element[] children = XMLUtil.getChildElements(restriction);
for (Element child : children) {
String nodeName = localName(child);
String value = child.getAttribute(VALUE);
if (ENUMERATION.equals(nodeName)) {
if (PrimitiveType.STRING.equals(descriptor.getPrimitiveType()))
descriptor.addValue("'" + value + "'");
else
descriptor.addValue(value);
} else if (MIN_INCLUSIVE.equals(nodeName)) {
descriptor.setMin(value);
descriptor.setMinInclusive(true);
} else if (MIN_EXCLUSIVE.equals(nodeName)) {
descriptor.setMin(value);
descriptor.setMinInclusive(false);
} else if (MAX_INCLUSIVE.equals(nodeName)) {
descriptor.setMax(value);
descriptor.setMaxInclusive(true);
} else if (MAX_EXCLUSIVE.equals(nodeName)) {
descriptor.setMax(value);
descriptor.setMaxInclusive(false);
} else if (LENGTH.equals(nodeName)) {
int length = Integer.parseInt(value);
descriptor.setMinLength(length);
descriptor.setMaxLength(length);
} else if (BeanUtil.hasProperty(descriptor.getClass(), nodeName)) {
BeanUtil.setPropertyValue(descriptor, nodeName, value, false);
} else
LOGGER.warn("Ignoring restriction " + nodeName + ": " + value);
}
}
private void parseImport(Element importElement) {
LOGGER.debug("parseImport()");
throw unsupportedElementType(importElement, null); // TODO v0.8 implement parseImport()
}
/** parses an XML Schema inclusion and adds its types to the {@link DataModel} */
private void parseStructureOfInclude(Element includeElement) throws IOException {
LOGGER.debug("parseStructureOfInclude()");
assert "include".equals(localName(includeElement));
String location = includeElement.getAttribute("schemaLocation");
parseStructure(parse(location));
}
private void parseDetailsOfInclude(Element includeElement) throws IOException {
LOGGER.debug("parseDetailsOfInclude()");
assert "include".equals(localName(includeElement));
String location = includeElement.getAttribute("schemaLocation");
parseDetails(parse(location));
}
private void parseGroup(Element group) {
LOGGER.debug("parseGroup()");
throw unsupportedElementType(group, null); // TODO v0.8 implement parseGroup()
}
private ComplexTypeDescriptor parseAttributeGroup(Element group) {
LOGGER.debug("parseAttributeGroup()");
// check if it's an attributeGroup reference
String refName = normalizedAttributeValue(group, REF);
if (refName != null) {
ComplexTypeDescriptor refdType = (ComplexTypeDescriptor) getType(refName);
if (refdType == null)
throw new ConfigurationError("referenced attributeGroup not found: " + refName);
return refdType;
}
// create a new attributeGroup
String name = normalizedAttributeValue(group, "name");
ComplexTypeDescriptor type = new ComplexTypeDescriptor(name, this);
Annotation annotation = null;
for (Element child : XMLUtil.getChildElements(group)) {
String elType = XMLUtil.localName(child);
if (ATTRIBUTE.equals(elType))
parseAttribute(child, type);
else if ("attributeGroup".equals(elType)) {
// TODO v0.8 map as parent relationship (could be several ones)
ComplexTypeDescriptor childGroup = parseAttributeGroup(child);
for (InstanceDescriptor component : childGroup.getParts())
type.addPart(component);
} else if ("annotation".equals(elType))
annotation = new Annotation(child);
else
throw unsupportedElementType(child, group);
}
if (annotation != null && annotation.getAppInfo() != null)
LOGGER.warn("ignoring appinfo of attributeGroup: " + name);
addTypeDescriptor(type);
return type;
}
private void parseSequence(Element sequence, ComplexTypeDescriptor owner) {
LOGGER.debug("parseSequence()"); // TODO v0.8 evaluate minCount/maxCount for sequence
parseComponentGroupChildren(sequence, owner);
}
private void parseChoice(Element choice, ComplexTypeDescriptor owner) {
LOGGER.debug("parseChoice()");
AlternativeGroupDescriptor choiceDescriptor = new AlternativeGroupDescriptor(null, this);
parseComponentGroupChildren(choice, choiceDescriptor);
PartDescriptor partDescriptor = new PartDescriptor(null, this, choiceDescriptor);
parseOccurrences(choice, partDescriptor);
owner.addComponent(partDescriptor);
}
private void parseAll(Element all, ComplexTypeDescriptor owner) {
LOGGER.debug("parseAll()"); // TODO v0.8 test
parseComponentGroupChildren(all, owner);
}
private void parseComponentGroupChildren(Element choice, ComplexTypeDescriptor groupDescriptor) {
Element[] children = XMLUtil.getChildElements(choice);
for (Element child : children) {
String nodeName = localName(child);
if (ELEMENT.equals(nodeName))
parseContainedElement(child, groupDescriptor);
else if (SEQUENCE.equals(nodeName))
parseSequence(child, groupDescriptor);
else if (CHOICE.equals(nodeName))
parseChoice(child, groupDescriptor);
else
throw unsupportedElementType(child, choice);
}
}
private UnsupportedOperationException unsupportedElementType(Element element, Element parent) {
String message = "Element type " + element.getNodeName() + " not supported";
if (parent != null)
message += " in " + parent.getNodeName();
return new UnsupportedOperationException(message);
}
}