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