/* * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Bogdan Stefanescu * Wojciech Sulejman * Florent Guillaume * Thierry Delprat * Nicolas Chapurlat <nchapurlat@nuxeo.com> */ package org.nuxeo.ecm.core.schema; import static com.sun.xml.xsom.XSFacet.FACET_ENUMERATION; import static com.sun.xml.xsom.XSFacet.FACET_LENGTH; import static com.sun.xml.xsom.XSFacet.FACET_MAXEXCLUSIVE; import static com.sun.xml.xsom.XSFacet.FACET_MAXINCLUSIVE; import static com.sun.xml.xsom.XSFacet.FACET_MAXLENGTH; import static com.sun.xml.xsom.XSFacet.FACET_MINEXCLUSIVE; import static com.sun.xml.xsom.XSFacet.FACET_MININCLUSIVE; import static com.sun.xml.xsom.XSFacet.FACET_MINLENGTH; import static com.sun.xml.xsom.XSFacet.FACET_PATTERN; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.schema.types.ComplexType; import org.nuxeo.ecm.core.schema.types.ComplexTypeImpl; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.ListType; import org.nuxeo.ecm.core.schema.types.ListTypeImpl; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.SchemaImpl; import org.nuxeo.ecm.core.schema.types.SimpleType; import org.nuxeo.ecm.core.schema.types.SimpleTypeImpl; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.core.schema.types.TypeBindingException; import org.nuxeo.ecm.core.schema.types.TypeException; import org.nuxeo.ecm.core.schema.types.constraints.Constraint; import org.nuxeo.ecm.core.schema.types.constraints.ConstraintUtils; import org.nuxeo.ecm.core.schema.types.constraints.DateIntervalConstraint; import org.nuxeo.ecm.core.schema.types.constraints.EnumConstraint; import org.nuxeo.ecm.core.schema.types.constraints.LengthConstraint; import org.nuxeo.ecm.core.schema.types.constraints.NotNullConstraint; import org.nuxeo.ecm.core.schema.types.constraints.NumericIntervalConstraint; import org.nuxeo.ecm.core.schema.types.constraints.ObjectResolverConstraint; import org.nuxeo.ecm.core.schema.types.constraints.PatternConstraint; import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolverService; import org.nuxeo.runtime.api.Framework; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.sun.xml.xsom.ForeignAttributes; import com.sun.xml.xsom.XSAttributeDecl; import com.sun.xml.xsom.XSAttributeUse; import com.sun.xml.xsom.XSComplexType; import com.sun.xml.xsom.XSContentType; import com.sun.xml.xsom.XSElementDecl; import com.sun.xml.xsom.XSFacet; import com.sun.xml.xsom.XSListSimpleType; import com.sun.xml.xsom.XSModelGroup; import com.sun.xml.xsom.XSParticle; import com.sun.xml.xsom.XSSchema; import com.sun.xml.xsom.XSSchemaSet; import com.sun.xml.xsom.XSTerm; import com.sun.xml.xsom.XSType; import com.sun.xml.xsom.XmlString; import com.sun.xml.xsom.impl.RestrictionSimpleTypeImpl; import com.sun.xml.xsom.parser.XSOMParser; /** * Loader of XSD schemas into Nuxeo Schema objects. */ public class XSDLoader { private static final String ATTR_CORE_EXTERNAL_REFERENCES = "resolver"; private static final Log log = LogFactory.getLog(XSDLoader.class); private static final String ANONYMOUS_TYPE_SUFFIX = "#anonymousType"; private static final String NAMESPACE_CORE_VALIDATION = "http://www.nuxeo.org/ecm/schemas/core/validation/"; private static final String NAMESPACE_CORE_EXTERNAL_REFERENCES = "http://www.nuxeo.org/ecm/schemas/core/external-references/"; private static final String NS_XSD = "http://www.w3.org/2001/XMLSchema"; protected final SchemaManagerImpl schemaManager; protected List<String> referencedXSD = new ArrayList<String>(); protected boolean collectReferencedXSD = false; protected SchemaBindingDescriptor sd; private ObjectResolverService referenceService; protected ObjectResolverService getObjectResolverService() { if (referenceService == null) { referenceService = Framework.getService(ObjectResolverService.class); } return referenceService; } public XSDLoader(SchemaManagerImpl schemaManager) { this.schemaManager = schemaManager; } public XSDLoader(SchemaManagerImpl schemaManager, SchemaBindingDescriptor sd) { this.schemaManager = schemaManager; this.sd = sd; } public XSDLoader(SchemaManagerImpl schemaManager, boolean collectReferencedXSD) { this.schemaManager = schemaManager; this.collectReferencedXSD = collectReferencedXSD; } protected void registerSchema(Schema schema) { schemaManager.registerSchema(schema); } protected Type getType(String name) { return schemaManager.getType(name); } protected XSOMParser getParser() { XSOMParser parser = new XSOMParser(); ErrorHandler errorHandler = new SchemaErrorHandler(); parser.setErrorHandler(errorHandler); if (sd != null) { parser.setEntityResolver(new NXSchemaResolver(schemaManager, sd)); } return parser; } protected static class NXSchemaResolver implements EntityResolver { protected SchemaManagerImpl schemaManager; protected SchemaBindingDescriptor sd; NXSchemaResolver(SchemaManagerImpl schemaManager, SchemaBindingDescriptor sd) { this.schemaManager = schemaManager; this.sd = sd; } @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { String[] parts = systemId.split("/" + SchemaManagerImpl.SCHEMAS_DIR_NAME + "/"); String importXSDSubPath = parts[1]; File xsd = new File(schemaManager.getSchemasDir(), importXSDSubPath); if (!xsd.exists()) { int idx = sd.src.lastIndexOf("/"); importXSDSubPath = sd.src.substring(0, idx + 1) + importXSDSubPath; URL url = sd.context.getLocalResource(importXSDSubPath); if (url == null) { // try asking the class loader url = sd.context.getResource(importXSDSubPath); } if (url != null) { return new InputSource(url.openStream()); } } return null; } } protected static class SchemaErrorHandler implements ErrorHandler { @Override public void error(SAXParseException e) throws SAXException { log.error("Error: " + e.getMessage()); throw e; } @Override public void fatalError(SAXParseException e) throws SAXException { log.error("FatalError: " + e.getMessage()); throw e; } @Override public void warning(SAXParseException e) throws SAXException { log.error("Warning: " + e.getMessage()); } } // called by SchemaManagerImpl public Schema loadSchema(String name, String prefix, File file) throws SAXException, IOException, TypeException { return loadSchema(name, prefix, file, null); } /** * Called by schema manager. * * @since 5.7 */ public Schema loadSchema(String name, String prefix, File file, String xsdElement) throws SAXException, IOException, TypeException { return loadSchema(name, prefix, file, null, false); } /** * @param isVersionWritable if true, the schema's fields will be writable even for Version document. * @since 8.4 */ public Schema loadSchema(String name, String prefix, File file, String xsdElement, boolean isVersionWritable) throws SAXException, IOException, TypeException { XSOMParser parser = getParser(); String systemId = file.toURI().toURL().toExternalForm(); if (file.getPath().startsWith("\\\\")) { // Windows UNC share // work around a bug in Xerces due to // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086147 // (xsom passes a systemId of the form file://server/share/... // but this is not parsed correctly when turned back into // a File object inside Xerces) systemId = systemId.replace("file://", "file:////"); } try { parser.parse(systemId); } catch (SAXParseException e) { throw new SAXException("Error parsing schema: " + systemId, e); } XSSchemaSet xsSchemas = parser.getResult(); if (collectReferencedXSD) { collectReferencedXSD(xsSchemas); } return loadSchema(name, prefix, xsSchemas, xsdElement, isVersionWritable); } protected void collectReferencedXSD(XSSchemaSet xsSchemas) { Collection<XSSchema> schemas = xsSchemas.getSchemas(); String ns = null; for (XSSchema s : schemas) { ns = s.getTargetNamespace(); if (ns.length() <= 0 || ns.equals(NS_XSD)) { continue; } String systemId = s.getLocator().getSystemId(); if (systemId != null && systemId.startsWith("file:/")) { String filePath = systemId.substring(6); if (!referencedXSD.contains(filePath)) { referencedXSD.add(filePath); } } } } /** * Create Nuxeo schema from a XSD resource. If xsdElement is non null and correspont to the name of a complex * element, the schema is created from the target complex type instead of from the global schema * * @since 5.7 * @param name schema name * @param prefix schema prefix * @param url url to load the XSD resource * @param xsdElement name of the complex element to use as root of the schema * @return * @throws SAXException * @throws TypeException * @since 5.7 */ public Schema loadSchema(String name, String prefix, URL url, String xsdElement) throws SAXException, TypeException { XSOMParser parser = getParser(); parser.parse(url); XSSchemaSet xsSchemas = parser.getResult(); return loadSchema(name, prefix, xsSchemas, xsdElement); } // called by tests public Schema loadSchema(String name, String prefix, URL url) throws SAXException, TypeException { return loadSchema(name, prefix, url, null); } /** * @since 8.4 */ protected Schema loadSchema(String name, String prefix, XSSchemaSet schemaSet, String xsdElement) throws SAXException, TypeException { return loadSchema(name, prefix, schemaSet, xsdElement, false); } protected Schema loadSchema(String name, String prefix, XSSchemaSet schemaSet, String xsdElement, boolean isVersionWritable) throws SAXException, TypeException { if (schemaSet == null) { return null; } Collection<XSSchema> schemas = schemaSet.getSchemas(); XSSchema schema = null; String ns = null; for (XSSchema s : schemas) { ns = s.getTargetNamespace(); if (ns.length() > 0 && !ns.equals(NS_XSD)) { schema = s; break; } } if (schema == null) { return null; } Schema ecmSchema = new SchemaImpl(name, new Namespace(ns, prefix), isVersionWritable); // load elements Collection<XSElementDecl> elements = schema.getElementDecls().values(); for (XSElementDecl el : elements) { // register the type if not yet registered Type ecmType = loadType(ecmSchema, el.getType(), el.getName()); if (ecmType != null) { // add the field to the schema createField(ecmSchema, el, ecmType); } else { log.warn("Failed to load field " + el.getName() + " : " + el.getType()); } } // load attributes Collection<XSAttributeDecl> attributes = schema.getAttributeDecls().values(); for (XSAttributeDecl att : attributes) { // register the type if not yet registered Type ecmType = loadType(ecmSchema, att.getType(), att.getName()); if (ecmType != null) { // add the field to the schema createField(ecmSchema, att, ecmType, true); } else { log.warn("Failed to load field from attribute " + att.getName() + " : " + att.getType()); } } if (xsdElement != null) { Field singleComplexField = ecmSchema.getField(xsdElement); if (singleComplexField == null) { log.warn("Unable to find element " + xsdElement + " to rebase schema " + name); } else { if (singleComplexField.getType().isComplexType()) { ComplexType singleComplexFieldType = (ComplexType) singleComplexField.getType(); ecmSchema = new SchemaImpl(singleComplexFieldType, name, new Namespace(ns, prefix), isVersionWritable); } else { log.warn("can not rebase schema " + name + " on " + xsdElement + " that is not a complex type"); } } } registerSchema(ecmSchema); return ecmSchema; } protected Type loadType(Schema schema, XSType type, String fieldName) throws TypeBindingException { String name; if (type.getName() == null || type.isLocal()) { name = getAnonymousTypeName(type, fieldName); if (name == null) { log.warn("Unable to load type - no name found"); return null; } } else { name = type.getName(); } // look into global types Type ecmType = getType(name); if (ecmType != null) { return ecmType; } // look into user types for this schema ecmType = schema.getType(name); if (ecmType != null) { return ecmType; } // maybe an alias to a primitive type? if (type.getTargetNamespace().equals(NS_XSD)) { ecmType = XSDTypes.getType(name); // find alias if (ecmType == null) { log.warn("Cannot use unknown XSD type: " + name); } return ecmType; } if (type.isSimpleType()) { if (type instanceof XSListSimpleType) { ecmType = loadListType(schema, (XSListSimpleType) type, fieldName); } else { ecmType = loadSimpleType(schema, type, fieldName); } } else { ecmType = loadComplexType(schema, name, type.asComplexType()); } if (ecmType != null) { schema.registerType(ecmType); } else { log.warn("loadType for " + fieldName + " of " + type + " returns null"); } return ecmType; } /** * @param name the type name (note, the type may have a null name if an anonymous type) * @param type * @return */ protected Type loadComplexType(Schema schema, String name, XSType type) throws TypeBindingException { XSType baseType = type.getBaseType(); ComplexType superType = null; // the anyType is the basetype of itself if (baseType.getBaseType() != baseType) { // have a base type if (baseType.isComplexType()) { superType = (ComplexType) loadType(schema, baseType, name); } else { log.warn("Complex type has a non complex type super type???"); } } XSComplexType xsct = type.asComplexType(); // try to get the delta content XSContentType content = xsct.getExplicitContent(); // if none get the entire content if (content == null) { content = xsct.getContentType(); } Type ret = createComplexType(schema, superType, name, content, xsct.isAbstract()); if (ret != null && ret instanceof ComplexType) { // load attributes if any loadAttributes(schema, xsct, (ComplexType) ret); } return ret; } protected void loadAttributes(Schema schema, XSComplexType xsct, ComplexType ct) throws TypeBindingException { Collection<? extends XSAttributeUse> attrs = xsct.getAttributeUses(); for (XSAttributeUse attr : attrs) { XSAttributeDecl at = attr.getDecl(); Type fieldType = loadType(schema, at.getType(), at.getName()); if (fieldType == null) { throw new TypeBindingException("Cannot add type for '" + at.getName() + "'"); } createField(ct, at, fieldType, !attr.isRequired()); } } protected SimpleType loadSimpleType(Schema schema, XSType type, String fieldName) throws TypeBindingException { String name = type.getName(); if (name == null) { // probably a local type name = fieldName + ANONYMOUS_TYPE_SUFFIX; } XSType baseType = type.getBaseType(); SimpleType superType = null; if (baseType != type) { // have a base type superType = (SimpleType) loadType(schema, baseType, fieldName); } SimpleTypeImpl simpleType = new SimpleTypeImpl(superType, schema.getName(), name); // add constraints/restrictions to the simple type if (type instanceof RestrictionSimpleTypeImpl) { RestrictionSimpleTypeImpl restrictionType = (RestrictionSimpleTypeImpl) type; List<Constraint> constraints = new ArrayList<Constraint>(); // pattern XSFacet patternFacet = restrictionType.getFacet(FACET_PATTERN); if (patternFacet != null) { if (simpleType.getPrimitiveType().support(PatternConstraint.class)) { // String pattern String pattern = patternFacet.getValue().toString(); Constraint constraint = new PatternConstraint(pattern); constraints.add(constraint); } else { logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_PATTERN); } } // length XSFacet minLengthFacet = restrictionType.getFacet(FACET_MINLENGTH); XSFacet maxLengthFacet = restrictionType.getFacet(FACET_MAXLENGTH); XSFacet lengthFacet = restrictionType.getFacet(FACET_LENGTH); if (maxLengthFacet != null || minLengthFacet != null || lengthFacet != null) { if (simpleType.getPrimitiveType().support(LengthConstraint.class)) { // String Length Object min = null, max = null; if (lengthFacet != null) { min = lengthFacet.getValue().toString(); max = min; } else { if (minLengthFacet != null) { min = minLengthFacet.getValue(); } if (maxLengthFacet != null) { max = maxLengthFacet.getValue(); } } Constraint constraint = new LengthConstraint(min, max); constraints.add(constraint); } else { logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_MINLENGTH, FACET_MAXLENGTH, FACET_LENGTH); } } // Intervals XSFacet minExclusiveFacet = restrictionType.getFacet(FACET_MINEXCLUSIVE); XSFacet minInclusiveFacet = restrictionType.getFacet(FACET_MININCLUSIVE); XSFacet maxExclusiveFacet = restrictionType.getFacet(FACET_MAXEXCLUSIVE); XSFacet maxInclusiveFacet = restrictionType.getFacet(FACET_MAXINCLUSIVE); if (minExclusiveFacet != null || minInclusiveFacet != null || maxExclusiveFacet != null || maxInclusiveFacet != null) { if (simpleType.getPrimitiveType().support(NumericIntervalConstraint.class)) { // Numeric Interval Object min = null, max = null; boolean includingMin = true, includingMax = true; if (minExclusiveFacet != null) { min = minExclusiveFacet.getValue(); includingMin = false; } else if (minInclusiveFacet != null) { min = minInclusiveFacet.getValue(); includingMin = true; } if (maxExclusiveFacet != null) { max = maxExclusiveFacet.getValue(); includingMax = false; } else if (maxInclusiveFacet != null) { max = maxInclusiveFacet.getValue(); includingMax = true; } Constraint constraint = new NumericIntervalConstraint(min, includingMin, max, includingMax); constraints.add(constraint); } else if (simpleType.getPrimitiveType().support(DateIntervalConstraint.class)) { // Date Interval Object min = null, max = null; boolean includingMin = true, includingMax = true; if (minExclusiveFacet != null) { min = minExclusiveFacet.getValue(); includingMin = false; } if (minInclusiveFacet != null) { min = minInclusiveFacet.getValue(); includingMin = true; } if (maxExclusiveFacet != null) { max = maxExclusiveFacet.getValue(); includingMax = false; } if (maxInclusiveFacet != null) { max = maxInclusiveFacet.getValue(); includingMax = true; } Constraint constraint = new DateIntervalConstraint(min, includingMin, max, includingMax); constraints.add(constraint); } else { logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_MINEXCLUSIVE, FACET_MININCLUSIVE, FACET_MAXEXCLUSIVE, FACET_MAXINCLUSIVE); } } // Enumeration List<XSFacet> enumFacets = restrictionType.getFacets("enumeration"); if (enumFacets != null && enumFacets.size() > 0) { if (simpleType.getPrimitiveType().support(EnumConstraint.class)) { // string enumeration List<String> enumValues = new ArrayList<String>(); for (XSFacet enumFacet : enumFacets) { enumValues.add(enumFacet.getValue().toString()); } Constraint constraint = new EnumConstraint(enumValues); constraints.add(constraint); } else { logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_ENUMERATION); } } String refName = restrictionType.getForeignAttribute(NAMESPACE_CORE_EXTERNAL_REFERENCES, ATTR_CORE_EXTERNAL_REFERENCES); Map<String, String> refParameters = new HashMap<String, String>(); for (ForeignAttributes attr : restrictionType.getForeignAttributes()) { for (int index = 0; index < attr.getLength(); index++) { String attrNS = attr.getURI(index); String attrName = attr.getLocalName(index); String attrValue = attr.getValue(index); if (NAMESPACE_CORE_EXTERNAL_REFERENCES.equals(attrNS)) { if (!ATTR_CORE_EXTERNAL_REFERENCES.equals(attrName)) { refParameters.put(attrName, attrValue); } } } } if (refName != null) { ObjectResolver resolver = getObjectResolverService().getResolver(refName, refParameters); if (resolver != null) { simpleType.setResolver(resolver); constraints.add(new ObjectResolverConstraint(resolver)); } else { log.info("type of " + fieldName + "|" + type.getName() + " targets ObjectResolver namespace but has no matching resolver registered " + "(please contribute to component : org.nuxeo.ecm.core.schema.ObjectResolverService)"); } } simpleType.addConstraints(constraints); } return simpleType; } private void logUnsupportedFacetRestriction(Schema schema, String fieldName, SimpleTypeImpl simpleType, String... facetNames) { StringBuilder msg = new StringBuilder(); msg.append("schema|field|type : ").append(schema.getName()); msg.append("|").append(fieldName); msg.append("|").append(simpleType.getPrimitiveType()); msg.append(" following restriction facet are not handled by constraints API for this type :"); for (String facetName : facetNames) { msg.append(facetName).append(" "); } log.warn(msg.toString()); } protected ListType loadListType(Schema schema, XSListSimpleType type, String fieldName) throws TypeBindingException { String name = type.getName(); if (name == null) { // probably a local type name = fieldName + ANONYMOUS_TYPE_SUFFIX; } XSType xsItemType = type.getItemType(); Type itemType; if (xsItemType.getTargetNamespace().equals(NS_XSD)) { itemType = XSDTypes.getType(xsItemType.getName()); } else { itemType = loadSimpleType(schema, xsItemType != null ? xsItemType : type.getSimpleBaseType(), null); } if (itemType == null) { log.error("list item type was not defined -> you should define first the item type"); return null; } return new ListTypeImpl(schema.getName(), name, itemType); } protected Type createComplexType(Schema schema, ComplexType superType, String name, XSContentType content, boolean abstractType) throws TypeBindingException { ComplexType ct = new ComplexTypeImpl(superType, schema.getName(), name); // -------- Workaround - we register now the complex type - to fix // recursive references to the same type schema.registerType(ct); // ------------------------------------------ XSParticle particle = content.asParticle(); if (particle == null) { // complex type without particle -> may be it contains only // attributes -> return it as is return ct; } XSTerm term = particle.getTerm(); XSModelGroup mg = term.asModelGroup(); return processModelGroup(schema, superType, name, ct, mg, abstractType); } protected Type createFakeComplexType(Schema schema, ComplexType superType, String name, XSModelGroup mg) throws TypeBindingException { ComplexType ct = new ComplexTypeImpl(superType, schema.getName(), name); // -------- Workaround - we register now the complex type - to fix // recursive references to the same type schema.registerType(ct); return processModelGroup(schema, superType, name, ct, mg, false); } protected Type processModelGroup(Schema schema, ComplexType superType, String name, ComplexType ct, XSModelGroup mg, boolean abstractType) throws TypeBindingException { if (mg == null) { // TODO don't know how to handle this for now throw new TypeBindingException("unsupported complex type"); } XSParticle[] group = mg.getChildren(); if (group.length == 0) { return null; } if (group.length == 1 && superType == null && group[0].isRepeated()) { // a list ? // only convert to list of type is not abstract if (!abstractType) { return createListType(schema, name, group[0]); } } for (XSParticle child : group) { XSTerm term = child.getTerm(); XSElementDecl element = term.asElementDecl(); int maxOccur = child.getMaxOccurs().intValue(); if (element == null) { // assume this is a xs:choice group // (did not find any other way to detect ! // // => make an aggregation of xs:choice subfields if (maxOccur < 0 || maxOccur > 1) { // means this is a list // // first create a fake complex type Type fakeType = createFakeComplexType(schema, superType, name + "#anonymousListItem", term.asModelGroup()); // wrap it as a list ListType listType = createListType(schema, name + "#anonymousListType", fakeType, 0, maxOccur); // add the listfield to the current CT String fieldName = ct.getName() + "#anonymousList"; ct.addField(fieldName, listType, null, 0, null); } else { processModelGroup(schema, superType, name, ct, term.asModelGroup(), abstractType); } } else { if (maxOccur < 0 || maxOccur > 1) { Type fieldType = loadType(schema, element.getType(), element.getName()); if (fieldType != null) { ListType listType = createListType(schema, element.getName() + "#anonymousListType", fieldType, 0, maxOccur); // add the listfield to the current CT String fieldName = element.getName(); ct.addField(fieldName, listType, null, 0, null); } } else { loadComplexTypeElement(schema, ct, element); } } } // add fields from Parent if (superType != null && superType.isComplexType()) { for (Field parentField : superType.getFields()) { ct.addField(parentField.getName().getLocalName(), parentField.getType(), (String) parentField.getDefaultValue(), 0, null); } } return ct; } protected ListType createListType(Schema schema, String name, XSParticle particle) throws TypeBindingException { XSElementDecl element = particle.getTerm().asElementDecl(); if (element == null) { log.warn("Ignoring " + name + " unsupported list type"); return null; } Type type = loadType(schema, element.getType(), element.getName()); if (type == null) { log.warn("Unable to find type for " + element.getName()); return null; } XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } int flags = 0; if (defValue == null) { dv = element.getFixedValue(); if (dv != null) { defValue = dv.value; flags |= Field.CONSTANT; } } boolean computedNillable = isNillable(element); if (computedNillable) { flags |= Field.NILLABLE; } Set<Constraint> constraints = new HashSet<Constraint>(); if (!computedNillable) { constraints.add(NotNullConstraint.get()); } if (type instanceof SimpleType) { SimpleType st = (SimpleType) type; constraints.addAll(st.getConstraints()); } return new ListTypeImpl(schema.getName(), name, type, element.getName(), defValue, flags, constraints, particle.getMinOccurs().intValue(), particle.getMaxOccurs().intValue()); } protected static ListType createListType(Schema schema, String name, Type itemType, int min, int max) throws TypeBindingException { String elementName = name + "#item"; return new ListTypeImpl(schema.getName(), name, itemType, elementName, null, min, max); } protected void loadComplexTypeElement(Schema schema, ComplexType type, XSElementDecl element) throws TypeBindingException { XSType elementType = element.getType(); Type fieldType = loadType(schema, elementType, element.getName()); if (fieldType != null) { createField(type, element, fieldType); } } protected static Field createField(ComplexType type, XSElementDecl element, Type fieldType) { String elementName = element.getName(); XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } int flags = 0; if (defValue == null) { dv = element.getFixedValue(); if (dv != null) { defValue = dv.value; flags |= Field.CONSTANT; } } boolean computedNillable = isNillable(element); if (computedNillable) { flags |= Field.NILLABLE; } Set<Constraint> constraints = new HashSet<Constraint>(); if (!computedNillable) { constraints.add(NotNullConstraint.get()); } if (fieldType instanceof SimpleType) { SimpleType st = (SimpleType) fieldType; constraints.addAll(st.getConstraints()); } Field field = type.addField(elementName, fieldType, defValue, flags, constraints); // set the max field length from the constraints if (fieldType instanceof SimpleTypeImpl) { LengthConstraint lc = ConstraintUtils.getConstraint(field.getConstraints(), LengthConstraint.class); if (lc != null && lc.getMax() != null) { field.setMaxLength(lc.getMax().intValue()); } } return field; } protected static Field createField(ComplexType type, XSAttributeDecl element, Type fieldType, boolean isNillable) { String elementName = element.getName(); XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } int flags = 0; if (defValue == null) { dv = element.getFixedValue(); if (dv != null) { defValue = dv.value; flags |= Field.CONSTANT; } } Set<Constraint> constraints = new HashSet<Constraint>(); if (!isNillable) { constraints.add(NotNullConstraint.get()); } if (fieldType.isSimpleType()) { constraints.addAll(((SimpleType) fieldType).getConstraints()); } return type.addField(elementName, fieldType, defValue, flags, constraints); } protected static String getAnonymousTypeName(XSType type, String fieldName) { if (type.isComplexType()) { XSElementDecl container = type.asComplexType().getScope(); String elName = container.getName(); return elName + ANONYMOUS_TYPE_SUFFIX; } else { return fieldName + ANONYMOUS_TYPE_SUFFIX; } } public List<String> getReferencedXSD() { return referencedXSD; } /** * ignore case where xsd:nillable is recognized as false by xsom (we don't know if it's not specified and we want to * preserve a default value to true. Therefore, we provide a custom attribute nxs:nillable to force nillable as * false) NB: if xsd:nillable is present and sets to true, deducted value will be true even if nxs:nillable is false * * @since 7.1 */ protected static boolean isNillable(XSElementDecl element) { boolean computedNillable; String value = element.getForeignAttribute(NAMESPACE_CORE_VALIDATION, "nillable"); if (!element.isNillable() && value != null && !Boolean.valueOf(value)) { computedNillable = false; } else { computedNillable = true; } return computedNillable; } }