001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Bogdan Stefanescu
018 *     Wojciech Sulejman
019 *     Florent Guillaume
020 *     Thierry Delprat
021 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
022 */
023package org.nuxeo.ecm.core.schema;
024
025import static com.sun.xml.xsom.XSFacet.FACET_ENUMERATION;
026import static com.sun.xml.xsom.XSFacet.FACET_LENGTH;
027import static com.sun.xml.xsom.XSFacet.FACET_MAXEXCLUSIVE;
028import static com.sun.xml.xsom.XSFacet.FACET_MAXINCLUSIVE;
029import static com.sun.xml.xsom.XSFacet.FACET_MAXLENGTH;
030import static com.sun.xml.xsom.XSFacet.FACET_MINEXCLUSIVE;
031import static com.sun.xml.xsom.XSFacet.FACET_MININCLUSIVE;
032import static com.sun.xml.xsom.XSFacet.FACET_MINLENGTH;
033import static com.sun.xml.xsom.XSFacet.FACET_PATTERN;
034
035import java.io.File;
036import java.io.IOException;
037import java.net.URL;
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.List;
043import java.util.Map;
044import java.util.Set;
045
046import org.apache.commons.logging.Log;
047import org.apache.commons.logging.LogFactory;
048import org.nuxeo.ecm.core.schema.types.ComplexType;
049import org.nuxeo.ecm.core.schema.types.ComplexTypeImpl;
050import org.nuxeo.ecm.core.schema.types.Field;
051import org.nuxeo.ecm.core.schema.types.ListType;
052import org.nuxeo.ecm.core.schema.types.ListTypeImpl;
053import org.nuxeo.ecm.core.schema.types.Schema;
054import org.nuxeo.ecm.core.schema.types.SchemaImpl;
055import org.nuxeo.ecm.core.schema.types.SimpleType;
056import org.nuxeo.ecm.core.schema.types.SimpleTypeImpl;
057import org.nuxeo.ecm.core.schema.types.Type;
058import org.nuxeo.ecm.core.schema.types.TypeBindingException;
059import org.nuxeo.ecm.core.schema.types.TypeException;
060import org.nuxeo.ecm.core.schema.types.constraints.Constraint;
061import org.nuxeo.ecm.core.schema.types.constraints.ConstraintUtils;
062import org.nuxeo.ecm.core.schema.types.constraints.DateIntervalConstraint;
063import org.nuxeo.ecm.core.schema.types.constraints.EnumConstraint;
064import org.nuxeo.ecm.core.schema.types.constraints.LengthConstraint;
065import org.nuxeo.ecm.core.schema.types.constraints.NotNullConstraint;
066import org.nuxeo.ecm.core.schema.types.constraints.NumericIntervalConstraint;
067import org.nuxeo.ecm.core.schema.types.constraints.ObjectResolverConstraint;
068import org.nuxeo.ecm.core.schema.types.constraints.PatternConstraint;
069import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver;
070import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolverService;
071import org.nuxeo.runtime.api.Framework;
072import org.xml.sax.EntityResolver;
073import org.xml.sax.ErrorHandler;
074import org.xml.sax.InputSource;
075import org.xml.sax.SAXException;
076import org.xml.sax.SAXParseException;
077
078import com.sun.xml.xsom.ForeignAttributes;
079import com.sun.xml.xsom.XSAttributeDecl;
080import com.sun.xml.xsom.XSAttributeUse;
081import com.sun.xml.xsom.XSComplexType;
082import com.sun.xml.xsom.XSContentType;
083import com.sun.xml.xsom.XSElementDecl;
084import com.sun.xml.xsom.XSFacet;
085import com.sun.xml.xsom.XSListSimpleType;
086import com.sun.xml.xsom.XSModelGroup;
087import com.sun.xml.xsom.XSParticle;
088import com.sun.xml.xsom.XSSchema;
089import com.sun.xml.xsom.XSSchemaSet;
090import com.sun.xml.xsom.XSTerm;
091import com.sun.xml.xsom.XSType;
092import com.sun.xml.xsom.XmlString;
093import com.sun.xml.xsom.impl.RestrictionSimpleTypeImpl;
094import com.sun.xml.xsom.parser.XSOMParser;
095
096/**
097 * Loader of XSD schemas into Nuxeo Schema objects.
098 */
099public class XSDLoader {
100
101    private static final String ATTR_CORE_EXTERNAL_REFERENCES = "resolver";
102
103    private static final Log log = LogFactory.getLog(XSDLoader.class);
104
105    private static final String ANONYMOUS_TYPE_SUFFIX = "#anonymousType";
106
107    private static final String NAMESPACE_CORE_VALIDATION = "http://www.nuxeo.org/ecm/schemas/core/validation/";
108
109    private static final String NAMESPACE_CORE_EXTERNAL_REFERENCES = "http://www.nuxeo.org/ecm/schemas/core/external-references/";
110
111    private static final String NS_XSD = "http://www.w3.org/2001/XMLSchema";
112
113    protected final SchemaManagerImpl schemaManager;
114
115    protected List<String> referencedXSD = new ArrayList<String>();
116
117    protected boolean collectReferencedXSD = false;
118
119    protected SchemaBindingDescriptor sd;
120
121    private ObjectResolverService referenceService;
122
123    protected ObjectResolverService getObjectResolverService() {
124        if (referenceService == null) {
125            referenceService = Framework.getService(ObjectResolverService.class);
126        }
127        return referenceService;
128    }
129
130    public XSDLoader(SchemaManagerImpl schemaManager) {
131        this.schemaManager = schemaManager;
132    }
133
134    public XSDLoader(SchemaManagerImpl schemaManager, SchemaBindingDescriptor sd) {
135        this.schemaManager = schemaManager;
136        this.sd = sd;
137    }
138
139    public XSDLoader(SchemaManagerImpl schemaManager, boolean collectReferencedXSD) {
140        this.schemaManager = schemaManager;
141        this.collectReferencedXSD = collectReferencedXSD;
142    }
143
144    protected void registerSchema(Schema schema) {
145        schemaManager.registerSchema(schema);
146    }
147
148    protected Type getType(String name) {
149        return schemaManager.getType(name);
150    }
151
152    protected XSOMParser getParser() {
153        XSOMParser parser = new XSOMParser();
154        ErrorHandler errorHandler = new SchemaErrorHandler();
155        parser.setErrorHandler(errorHandler);
156        if (sd != null) {
157            parser.setEntityResolver(new NXSchemaResolver(schemaManager, sd));
158        }
159        return parser;
160    }
161
162    protected static class NXSchemaResolver implements EntityResolver {
163
164        protected SchemaManagerImpl schemaManager;
165
166        protected SchemaBindingDescriptor sd;
167
168        NXSchemaResolver(SchemaManagerImpl schemaManager, SchemaBindingDescriptor sd) {
169            this.schemaManager = schemaManager;
170            this.sd = sd;
171        }
172
173        @Override
174        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
175
176            String[] parts = systemId.split("/" + SchemaManagerImpl.SCHEMAS_DIR_NAME + "/");
177            String importXSDSubPath = parts[1];
178
179            File xsd = new File(schemaManager.getSchemasDir(), importXSDSubPath);
180            if (!xsd.exists()) {
181                int idx = sd.src.lastIndexOf("/");
182                importXSDSubPath = sd.src.substring(0, idx + 1) + importXSDSubPath;
183                URL url = sd.context.getLocalResource(importXSDSubPath);
184                if (url == null) {
185                    // try asking the class loader
186                    url = sd.context.getResource(importXSDSubPath);
187                }
188                if (url != null) {
189                    return new InputSource(url.openStream());
190                }
191            }
192
193            return null;
194        }
195
196    }
197
198    protected static class SchemaErrorHandler implements ErrorHandler {
199        @Override
200        public void error(SAXParseException e) throws SAXException {
201            log.error("Error: " + e.getMessage());
202            throw e;
203        }
204
205        @Override
206        public void fatalError(SAXParseException e) throws SAXException {
207            log.error("FatalError: " + e.getMessage());
208            throw e;
209        }
210
211        @Override
212        public void warning(SAXParseException e) throws SAXException {
213            log.error("Warning: " + e.getMessage());
214        }
215    }
216
217    // called by SchemaManagerImpl
218    public Schema loadSchema(String name, String prefix, File file) throws SAXException, IOException, TypeException {
219        return loadSchema(name, prefix, file, null);
220    }
221
222    // called by SchemaManagerImpl
223    // @since 5.7
224    public Schema loadSchema(String name, String prefix, File file, String xsdElement)
225            throws SAXException, IOException, TypeException {
226        XSOMParser parser = getParser();
227        String systemId = file.toURI().toURL().toExternalForm();
228        if (file.getPath().startsWith("\\\\")) { // Windows UNC share
229            // work around a bug in Xerces due to
230            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086147
231            // (xsom passes a systemId of the form file://server/share/...
232            // but this is not parsed correctly when turned back into
233            // a File object inside Xerces)
234            systemId = systemId.replace("file://", "file:////");
235        }
236        try {
237            parser.parse(systemId);
238        } catch (SAXParseException e) {
239            throw new SAXException("Error parsing schema: " + systemId, e);
240        }
241
242        XSSchemaSet xsSchemas = parser.getResult();
243        if (collectReferencedXSD) {
244            collectReferencedXSD(xsSchemas);
245        }
246        return loadSchema(name, prefix, xsSchemas, xsdElement);
247    }
248
249    protected void collectReferencedXSD(XSSchemaSet xsSchemas) {
250
251        Collection<XSSchema> schemas = xsSchemas.getSchemas();
252        String ns = null;
253        for (XSSchema s : schemas) {
254            ns = s.getTargetNamespace();
255            if (ns.length() <= 0 || ns.equals(NS_XSD)) {
256                continue;
257            }
258
259            String systemId = s.getLocator().getSystemId();
260            if (systemId != null && systemId.startsWith("file:/")) {
261                String filePath = systemId.substring(6);
262                if (!referencedXSD.contains(filePath)) {
263                    referencedXSD.add(filePath);
264                }
265            }
266        }
267
268    }
269
270    /**
271     * Create Nuxeo schema from a XSD resource. If xsdElement is non null and correspont to the name of a complex
272     * element, the schema is created from the target complex type instead of from the global schema
273     *
274     * @since 5.7
275     * @param name schema name
276     * @param prefix schema prefix
277     * @param url url to load the XSD resource
278     * @param xsdElement name of the complex element to use as root of the schema
279     * @return
280     * @throws SAXException
281     * @throws TypeException
282     * @since 5.7
283     */
284    public Schema loadSchema(String name, String prefix, URL url, String xsdElement) throws SAXException, TypeException {
285        XSOMParser parser = getParser();
286        parser.parse(url);
287        XSSchemaSet xsSchemas = parser.getResult();
288        return loadSchema(name, prefix, xsSchemas, xsdElement);
289    }
290
291    // called by tests
292    public Schema loadSchema(String name, String prefix, URL url) throws SAXException, TypeException {
293        return loadSchema(name, prefix, url, null);
294    }
295
296    protected Schema loadSchema(String name, String prefix, XSSchemaSet schemaSet, String xsdElement)
297            throws SAXException, TypeException {
298        if (schemaSet == null) {
299            return null;
300        }
301        Collection<XSSchema> schemas = schemaSet.getSchemas();
302        XSSchema schema = null;
303        String ns = null;
304        for (XSSchema s : schemas) {
305            ns = s.getTargetNamespace();
306            if (ns.length() > 0 && !ns.equals(NS_XSD)) {
307                schema = s;
308                break;
309            }
310        }
311        if (schema == null) {
312            return null;
313        }
314        Schema ecmSchema = new SchemaImpl(name, new Namespace(ns, prefix));
315
316        // load elements
317        Collection<XSElementDecl> elements = schema.getElementDecls().values();
318        for (XSElementDecl el : elements) {
319            // register the type if not yet registered
320            Type ecmType = loadType(ecmSchema, el.getType(), el.getName());
321            if (ecmType != null) {
322                // add the field to the schema
323                createField(ecmSchema, el, ecmType);
324            } else {
325                log.warn("Failed to load field " + el.getName() + " : " + el.getType());
326            }
327        }
328
329        // load attributes
330        Collection<XSAttributeDecl> attributes = schema.getAttributeDecls().values();
331        for (XSAttributeDecl att : attributes) {
332            // register the type if not yet registered
333            Type ecmType = loadType(ecmSchema, att.getType(), att.getName());
334            if (ecmType != null) {
335                // add the field to the schema
336                createField(ecmSchema, att, ecmType, true);
337            } else {
338                log.warn("Failed to load field from attribute " + att.getName() + " : " + att.getType());
339            }
340        }
341
342        if (xsdElement != null) {
343            Field singleComplexField = ecmSchema.getField(xsdElement);
344            if (singleComplexField == null) {
345                log.warn("Unable to find element " + xsdElement + " to rebase schema " + name);
346            } else {
347                if (singleComplexField.getType().isComplexType()) {
348                    ComplexType singleComplexFieldType = (ComplexType) singleComplexField.getType();
349                    ecmSchema = new SchemaImpl(singleComplexFieldType, name, new Namespace(ns, prefix));
350                } else {
351                    log.warn("can not rebase schema " + name + " on " + xsdElement + " that is not a complex type");
352                }
353            }
354        }
355
356        registerSchema(ecmSchema);
357        return ecmSchema;
358    }
359
360    protected Type loadType(Schema schema, XSType type, String fieldName) throws TypeBindingException {
361        String name;
362        if (type.getName() == null || type.isLocal()) {
363            name = getAnonymousTypeName(type, fieldName);
364            if (name == null) {
365                log.warn("Unable to load type - no name found");
366                return null;
367            }
368        } else {
369            name = type.getName();
370        }
371        // look into global types
372        Type ecmType = getType(name);
373        if (ecmType != null) {
374            return ecmType;
375        }
376        // look into user types for this schema
377        ecmType = schema.getType(name);
378        if (ecmType != null) {
379            return ecmType;
380        }
381        // maybe an alias to a primitive type?
382        if (type.getTargetNamespace().equals(NS_XSD)) {
383            ecmType = XSDTypes.getType(name); // find alias
384            if (ecmType == null) {
385                log.warn("Cannot use unknown XSD type: " + name);
386            }
387            return ecmType;
388        }
389        if (type.isSimpleType()) {
390            if (type instanceof XSListSimpleType) {
391                ecmType = loadListType(schema, (XSListSimpleType) type, fieldName);
392            } else {
393                ecmType = loadSimpleType(schema, type, fieldName);
394            }
395        } else {
396            ecmType = loadComplexType(schema, name, type.asComplexType());
397        }
398        if (ecmType != null) {
399            schema.registerType(ecmType);
400        } else {
401            log.warn("loadType for " + fieldName + " of " + type + " returns null");
402        }
403        return ecmType;
404    }
405
406    /**
407     * @param name the type name (note, the type may have a null name if an anonymous type)
408     * @param type
409     * @return
410     */
411    protected Type loadComplexType(Schema schema, String name, XSType type) throws TypeBindingException {
412        XSType baseType = type.getBaseType();
413        ComplexType superType = null;
414        // the anyType is the basetype of itself
415        if (baseType.getBaseType() != baseType) { // have a base type
416            if (baseType.isComplexType()) {
417                superType = (ComplexType) loadType(schema, baseType, name);
418            } else {
419                log.warn("Complex type has a non complex type super type???");
420            }
421        }
422        XSComplexType xsct = type.asComplexType();
423        // try to get the delta content
424        XSContentType content = xsct.getExplicitContent();
425        // if none get the entire content
426        if (content == null) {
427            content = xsct.getContentType();
428        }
429        Type ret = createComplexType(schema, superType, name, content, xsct.isAbstract());
430        if (ret != null && ret instanceof ComplexType) {
431            // load attributes if any
432            loadAttributes(schema, xsct, (ComplexType) ret);
433        }
434
435        return ret;
436    }
437
438    protected void loadAttributes(Schema schema, XSComplexType xsct, ComplexType ct) throws TypeBindingException {
439        Collection<? extends XSAttributeUse> attrs = xsct.getAttributeUses();
440        for (XSAttributeUse attr : attrs) {
441            XSAttributeDecl at = attr.getDecl();
442            Type fieldType = loadType(schema, at.getType(), at.getName());
443            if (fieldType == null) {
444                throw new TypeBindingException("Cannot add type for '" + at.getName() + "'");
445            }
446            createField(ct, at, fieldType, !attr.isRequired());
447        }
448    }
449
450    protected SimpleType loadSimpleType(Schema schema, XSType type, String fieldName) throws TypeBindingException {
451        String name = type.getName();
452        if (name == null) {
453            // probably a local type
454            name = fieldName + ANONYMOUS_TYPE_SUFFIX;
455        }
456        XSType baseType = type.getBaseType();
457        SimpleType superType = null;
458        if (baseType != type) {
459            // have a base type
460            superType = (SimpleType) loadType(schema, baseType, fieldName);
461        }
462        SimpleTypeImpl simpleType = new SimpleTypeImpl(superType, schema.getName(), name);
463
464        // add constraints/restrictions to the simple type
465        if (type instanceof RestrictionSimpleTypeImpl) {
466            RestrictionSimpleTypeImpl restrictionType = (RestrictionSimpleTypeImpl) type;
467
468            List<Constraint> constraints = new ArrayList<Constraint>();
469
470            // pattern
471            XSFacet patternFacet = restrictionType.getFacet(FACET_PATTERN);
472            if (patternFacet != null) {
473                if (simpleType.getPrimitiveType().support(PatternConstraint.class)) {
474                    // String pattern
475                    String pattern = patternFacet.getValue().toString();
476                    Constraint constraint = new PatternConstraint(pattern);
477                    constraints.add(constraint);
478                } else {
479                    logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_PATTERN);
480                }
481            }
482
483            // length
484            XSFacet minLengthFacet = restrictionType.getFacet(FACET_MINLENGTH);
485            XSFacet maxLengthFacet = restrictionType.getFacet(FACET_MAXLENGTH);
486            XSFacet lengthFacet = restrictionType.getFacet(FACET_LENGTH);
487            if (maxLengthFacet != null || minLengthFacet != null || lengthFacet != null) {
488                if (simpleType.getPrimitiveType().support(LengthConstraint.class)) {
489                    // String Length
490                    Object min = null, max = null;
491                    if (lengthFacet != null) {
492                        min = lengthFacet.getValue().toString();
493                        max = min;
494                    } else {
495                        if (minLengthFacet != null) {
496                            min = minLengthFacet.getValue();
497                        }
498                        if (maxLengthFacet != null) {
499                            max = maxLengthFacet.getValue();
500                        }
501                    }
502                    Constraint constraint = new LengthConstraint(min, max);
503                    constraints.add(constraint);
504                } else {
505                    logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_MINLENGTH, FACET_MAXLENGTH,
506                            FACET_LENGTH);
507                }
508            }
509
510            // Intervals
511            XSFacet minExclusiveFacet = restrictionType.getFacet(FACET_MINEXCLUSIVE);
512            XSFacet minInclusiveFacet = restrictionType.getFacet(FACET_MININCLUSIVE);
513            XSFacet maxExclusiveFacet = restrictionType.getFacet(FACET_MAXEXCLUSIVE);
514            XSFacet maxInclusiveFacet = restrictionType.getFacet(FACET_MAXINCLUSIVE);
515            if (minExclusiveFacet != null || minInclusiveFacet != null || maxExclusiveFacet != null
516                    || maxInclusiveFacet != null) {
517                if (simpleType.getPrimitiveType().support(NumericIntervalConstraint.class)) {
518                    // Numeric Interval
519                    Object min = null, max = null;
520                    boolean includingMin = true, includingMax = true;
521                    if (minExclusiveFacet != null) {
522                        min = minExclusiveFacet.getValue();
523                        includingMin = false;
524                    } else if (minInclusiveFacet != null) {
525                        min = minInclusiveFacet.getValue();
526                        includingMin = true;
527                    }
528                    if (maxExclusiveFacet != null) {
529                        max = maxExclusiveFacet.getValue();
530                        includingMax = false;
531                    } else if (maxInclusiveFacet != null) {
532                        max = maxInclusiveFacet.getValue();
533                        includingMax = true;
534                    }
535                    Constraint constraint = new NumericIntervalConstraint(min, includingMin, max, includingMax);
536                    constraints.add(constraint);
537                } else if (simpleType.getPrimitiveType().support(DateIntervalConstraint.class)) {
538                    // Date Interval
539                    Object min = null, max = null;
540                    boolean includingMin = true, includingMax = true;
541                    if (minExclusiveFacet != null) {
542                        min = minExclusiveFacet.getValue();
543                        includingMin = false;
544                    }
545                    if (minInclusiveFacet != null) {
546                        min = minInclusiveFacet.getValue();
547                        includingMin = true;
548                    }
549                    if (maxExclusiveFacet != null) {
550                        max = maxExclusiveFacet.getValue();
551                        includingMax = false;
552                    }
553                    if (maxInclusiveFacet != null) {
554                        max = maxInclusiveFacet.getValue();
555                        includingMax = true;
556                    }
557                    Constraint constraint = new DateIntervalConstraint(min, includingMin, max, includingMax);
558                    constraints.add(constraint);
559                } else {
560                    logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_MINEXCLUSIVE,
561                            FACET_MININCLUSIVE, FACET_MAXEXCLUSIVE, FACET_MAXINCLUSIVE);
562                }
563            }
564
565            // Enumeration
566            List<XSFacet> enumFacets = restrictionType.getFacets("enumeration");
567            if (enumFacets != null && enumFacets.size() > 0) {
568                if (simpleType.getPrimitiveType().support(EnumConstraint.class)) {
569                    // string enumeration
570                    List<String> enumValues = new ArrayList<String>();
571                    for (XSFacet enumFacet : enumFacets) {
572                        enumValues.add(enumFacet.getValue().toString());
573                    }
574                    Constraint constraint = new EnumConstraint(enumValues);
575                    constraints.add(constraint);
576                } else {
577                    logUnsupportedFacetRestriction(schema, fieldName, simpleType, FACET_ENUMERATION);
578                }
579            }
580
581            String refName = restrictionType.getForeignAttribute(NAMESPACE_CORE_EXTERNAL_REFERENCES,
582                    ATTR_CORE_EXTERNAL_REFERENCES);
583            Map<String, String> refParameters = new HashMap<String, String>();
584            for (ForeignAttributes attr : restrictionType.getForeignAttributes()) {
585                for (int index = 0; index < attr.getLength(); index++) {
586                    String attrNS = attr.getURI(index);
587                    String attrName = attr.getLocalName(index);
588                    String attrValue = attr.getValue(index);
589                    if (NAMESPACE_CORE_EXTERNAL_REFERENCES.equals(attrNS)) {
590                        if (!ATTR_CORE_EXTERNAL_REFERENCES.equals(attrName)) {
591                            refParameters.put(attrName, attrValue);
592                        }
593                    }
594                }
595            }
596            if (refName != null) {
597                ObjectResolver resolver = getObjectResolverService().getResolver(refName, refParameters);
598                if (resolver != null) {
599                    simpleType.setResolver(resolver);
600                    constraints.add(new ObjectResolverConstraint(resolver));
601                } else {
602                    log.info("type of " + fieldName + "|" + type.getName()
603                            + " targets ObjectResolver namespace but has no matching resolver registered "
604                            + "(please contribute to component : org.nuxeo.ecm.core.schema.ObjectResolverService)");
605                }
606            }
607
608            simpleType.addConstraints(constraints);
609        }
610
611        return simpleType;
612    }
613
614    private void logUnsupportedFacetRestriction(Schema schema, String fieldName, SimpleTypeImpl simpleType,
615            String... facetNames) {
616        StringBuilder msg = new StringBuilder();
617        msg.append("schema|field|type : ").append(schema.getName());
618        msg.append("|").append(fieldName);
619        msg.append("|").append(simpleType.getPrimitiveType());
620        msg.append(" following restriction facet are not handled by constraints API for this type :");
621        for (String facetName : facetNames) {
622            msg.append(facetName).append(" ");
623        }
624        log.warn(msg.toString());
625    }
626
627    protected ListType loadListType(Schema schema, XSListSimpleType type, String fieldName) throws TypeBindingException {
628        String name = type.getName();
629        if (name == null) {
630            // probably a local type
631            name = fieldName + ANONYMOUS_TYPE_SUFFIX;
632        }
633        XSType xsItemType = type.getItemType();
634        Type itemType;
635        if (xsItemType.getTargetNamespace().equals(NS_XSD)) {
636            itemType = XSDTypes.getType(xsItemType.getName());
637        } else {
638            itemType = loadSimpleType(schema, xsItemType != null ? xsItemType : type.getSimpleBaseType(), null);
639        }
640        if (itemType == null) {
641            log.error("list item type was not defined -> you should define first the item type");
642            return null;
643        }
644        return new ListTypeImpl(schema.getName(), name, itemType);
645    }
646
647    protected Type createComplexType(Schema schema, ComplexType superType, String name, XSContentType content,
648            boolean abstractType) throws TypeBindingException {
649
650        ComplexType ct = new ComplexTypeImpl(superType, schema.getName(), name);
651
652        // -------- Workaround - we register now the complex type - to fix
653        // recursive references to the same type
654        schema.registerType(ct);
655
656        // ------------------------------------------
657        XSParticle particle = content.asParticle();
658        if (particle == null) {
659            // complex type without particle -> may be it contains only
660            // attributes -> return it as is
661            return ct;
662        }
663        XSTerm term = particle.getTerm();
664        XSModelGroup mg = term.asModelGroup();
665
666        return processModelGroup(schema, superType, name, ct, mg, abstractType);
667    }
668
669    protected Type createFakeComplexType(Schema schema, ComplexType superType, String name, XSModelGroup mg)
670            throws TypeBindingException {
671
672        ComplexType ct = new ComplexTypeImpl(superType, schema.getName(), name);
673        // -------- Workaround - we register now the complex type - to fix
674        // recursive references to the same type
675        schema.registerType(ct);
676
677        return processModelGroup(schema, superType, name, ct, mg, false);
678    }
679
680    protected Type processModelGroup(Schema schema, ComplexType superType, String name, ComplexType ct,
681            XSModelGroup mg, boolean abstractType) throws TypeBindingException {
682        if (mg == null) {
683            // TODO don't know how to handle this for now
684            throw new TypeBindingException("unsupported complex type");
685        }
686        XSParticle[] group = mg.getChildren();
687        if (group.length == 0) {
688            return null;
689        }
690        if (group.length == 1 && superType == null && group[0].isRepeated()) {
691            // a list ?
692            // only convert to list of type is not abstract
693            if (!abstractType) {
694                return createListType(schema, name, group[0]);
695            }
696        }
697        for (XSParticle child : group) {
698            XSTerm term = child.getTerm();
699            XSElementDecl element = term.asElementDecl();
700            int maxOccur = child.getMaxOccurs().intValue();
701
702            if (element == null) {
703                // assume this is a xs:choice group
704                // (did not find any other way to detect !
705                //
706                // => make an aggregation of xs:choice subfields
707                if (maxOccur < 0 || maxOccur > 1) {
708                    // means this is a list
709                    //
710                    // first create a fake complex type
711                    Type fakeType = createFakeComplexType(schema, superType, name + "#anonymousListItem",
712                            term.asModelGroup());
713                    // wrap it as a list
714                    ListType listType = createListType(schema, name + "#anonymousListType", fakeType, 0, maxOccur);
715                    // add the listfield to the current CT
716                    String fieldName = ct.getName() + "#anonymousList";
717                    ct.addField(fieldName, listType, null, 0, null);
718                } else {
719                    processModelGroup(schema, superType, name, ct, term.asModelGroup(), abstractType);
720                }
721            } else {
722                if (maxOccur < 0 || maxOccur > 1) {
723                    Type fieldType = loadType(schema, element.getType(), element.getName());
724                    if (fieldType != null) {
725                        ListType listType = createListType(schema, element.getName() + "#anonymousListType", fieldType,
726                                0, maxOccur);
727                        // add the listfield to the current CT
728                        String fieldName = element.getName();
729                        ct.addField(fieldName, listType, null, 0, null);
730                    }
731                } else {
732                    loadComplexTypeElement(schema, ct, element);
733                }
734            }
735        }
736
737        // add fields from Parent
738        if (superType != null && superType.isComplexType()) {
739            for (Field parentField : superType.getFields()) {
740                ct.addField(parentField.getName().getLocalName(), parentField.getType(),
741                        (String) parentField.getDefaultValue(), 0, null);
742            }
743        }
744        return ct;
745    }
746
747    protected ListType createListType(Schema schema, String name, XSParticle particle) throws TypeBindingException {
748        XSElementDecl element = particle.getTerm().asElementDecl();
749        if (element == null) {
750            log.warn("Ignoring " + name + " unsupported list type");
751            return null;
752        }
753        Type type = loadType(schema, element.getType(), element.getName());
754        if (type == null) {
755            log.warn("Unable to find type for " + element.getName());
756            return null;
757        }
758
759        XmlString dv = element.getDefaultValue();
760        String defValue = null;
761        if (dv != null) {
762            defValue = dv.value;
763        }
764        int flags = 0;
765        if (defValue == null) {
766            dv = element.getFixedValue();
767            if (dv != null) {
768                defValue = dv.value;
769                flags |= Field.CONSTANT;
770            }
771        }
772        boolean computedNillable = isNillable(element);
773        if (computedNillable) {
774            flags |= Field.NILLABLE;
775        }
776
777        Set<Constraint> constraints = new HashSet<Constraint>();
778        if (!computedNillable) {
779            constraints.add(NotNullConstraint.get());
780        }
781        if (type instanceof SimpleType) {
782            SimpleType st = (SimpleType) type;
783            constraints.addAll(st.getConstraints());
784        }
785
786        return new ListTypeImpl(schema.getName(), name, type, element.getName(), defValue, flags, constraints,
787                particle.getMinOccurs().intValue(), particle.getMaxOccurs().intValue());
788    }
789
790    protected static ListType createListType(Schema schema, String name, Type itemType, int min, int max)
791            throws TypeBindingException {
792        String elementName = name + "#item";
793        return new ListTypeImpl(schema.getName(), name, itemType, elementName, null, min, max);
794    }
795
796    protected void loadComplexTypeElement(Schema schema, ComplexType type, XSElementDecl element)
797            throws TypeBindingException {
798        XSType elementType = element.getType();
799
800        Type fieldType = loadType(schema, elementType, element.getName());
801        if (fieldType != null) {
802            createField(type, element, fieldType);
803        }
804    }
805
806    protected static Field createField(ComplexType type, XSElementDecl element, Type fieldType) {
807        String elementName = element.getName();
808        XmlString dv = element.getDefaultValue();
809        String defValue = null;
810        if (dv != null) {
811            defValue = dv.value;
812        }
813        int flags = 0;
814        if (defValue == null) {
815            dv = element.getFixedValue();
816            if (dv != null) {
817                defValue = dv.value;
818                flags |= Field.CONSTANT;
819            }
820        }
821
822        boolean computedNillable = isNillable(element);
823
824        if (computedNillable) {
825            flags |= Field.NILLABLE;
826        }
827
828        Set<Constraint> constraints = new HashSet<Constraint>();
829        if (!computedNillable) {
830            constraints.add(NotNullConstraint.get());
831        }
832        if (fieldType instanceof SimpleType) {
833            SimpleType st = (SimpleType) fieldType;
834            constraints.addAll(st.getConstraints());
835        }
836        Field field = type.addField(elementName, fieldType, defValue, flags, constraints);
837
838        // set the max field length from the constraints
839        if (fieldType instanceof SimpleTypeImpl) {
840            LengthConstraint lc = ConstraintUtils.getConstraint(field.getConstraints(), LengthConstraint.class);
841            if (lc != null && lc.getMax() != null) {
842                field.setMaxLength(lc.getMax().intValue());
843            }
844        }
845
846        return field;
847    }
848
849    protected static Field createField(ComplexType type, XSAttributeDecl element, Type fieldType, boolean isNillable) {
850        String elementName = element.getName();
851        XmlString dv = element.getDefaultValue();
852        String defValue = null;
853        if (dv != null) {
854            defValue = dv.value;
855        }
856        int flags = 0;
857        if (defValue == null) {
858            dv = element.getFixedValue();
859            if (dv != null) {
860                defValue = dv.value;
861                flags |= Field.CONSTANT;
862            }
863        }
864        Set<Constraint> constraints = new HashSet<Constraint>();
865        if (!isNillable) {
866            constraints.add(NotNullConstraint.get());
867        }
868        if (fieldType.isSimpleType()) {
869            constraints.addAll(((SimpleType) fieldType).getConstraints());
870        }
871        return type.addField(elementName, fieldType, defValue, flags, constraints);
872    }
873
874    protected static String getAnonymousTypeName(XSType type, String fieldName) {
875        if (type.isComplexType()) {
876            XSElementDecl container = type.asComplexType().getScope();
877            String elName = container.getName();
878            return elName + ANONYMOUS_TYPE_SUFFIX;
879        } else {
880            return fieldName + ANONYMOUS_TYPE_SUFFIX;
881        }
882    }
883
884    public List<String> getReferencedXSD() {
885        return referencedXSD;
886    }
887
888    /**
889     * ignore case where xsd:nillable is recognized as false by xsom (we don't know if it's not specified and we want to
890     * preserve a default value to true. Therefore, we provide a custom attribute nxs:nillable to force nillable as
891     * false) NB: if xsd:nillable is present and sets to true, deducted value will be true even if nxs:nillable is false
892     *
893     * @since 7.1
894     */
895    protected static boolean isNillable(XSElementDecl element) {
896        boolean computedNillable;
897        String value = element.getForeignAttribute(NAMESPACE_CORE_VALIDATION, "nillable");
898        if (!element.isNillable() && value != null && !Boolean.valueOf(value)) {
899            computedNillable = false;
900        } else {
901            computedNillable = true;
902        }
903        return computedNillable;
904    }
905
906}