001/*
002 * (C) Copyright 2006-2015 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 *     Florent Guillaume
019 */
020
021package org.nuxeo.ecm.core.schema;
022
023import java.io.File;
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.HashSet;
033import java.util.LinkedHashMap;
034import java.util.LinkedHashSet;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038import java.util.concurrent.ConcurrentHashMap;
039
040import org.apache.commons.lang.StringUtils;
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043
044import org.nuxeo.common.Environment;
045import org.nuxeo.common.utils.FileUtils;
046import org.nuxeo.ecm.core.schema.types.AnyType;
047import org.nuxeo.ecm.core.schema.types.ComplexType;
048import org.nuxeo.ecm.core.schema.types.CompositeType;
049import org.nuxeo.ecm.core.schema.types.CompositeTypeImpl;
050import org.nuxeo.ecm.core.schema.types.Field;
051import org.nuxeo.ecm.core.schema.types.ListType;
052import org.nuxeo.ecm.core.schema.types.QName;
053import org.nuxeo.ecm.core.schema.types.Schema;
054import org.nuxeo.ecm.core.schema.types.Type;
055import org.nuxeo.ecm.core.schema.types.TypeException;
056
057import org.xml.sax.SAXException;
058
059/**
060 * Schema Manager implementation.
061 * <p>
062 * Holds basic types (String, Integer, etc.), schemas, document types and facets.
063 */
064public class SchemaManagerImpl implements SchemaManager {
065
066    private static final Log log = LogFactory.getLog(SchemaManagerImpl.class);
067
068    /**
069     * Whether there have been changes to the registered schemas, facets or document types that require recomputation of
070     * the effective ones.
071     */
072    // volatile to use double-check idiom
073    protected volatile boolean dirty = true;
074
075    /** Basic type registry. */
076    protected Map<String, Type> types = new HashMap<>();
077
078    /** All the registered configurations (prefetch). */
079    protected List<TypeConfiguration> allConfigurations = new ArrayList<>();
080
081    /** All the registered schemas. */
082    protected List<SchemaBindingDescriptor> allSchemas = new ArrayList<>();
083
084    /** All the registered facets. */
085    protected List<FacetDescriptor> allFacets = new ArrayList<>();
086
087    /** All the registered document types. */
088    protected List<DocumentTypeDescriptor> allDocumentTypes = new ArrayList<>();
089
090    /** All the registered proxy descriptors. */
091    protected List<ProxiesDescriptor> allProxies = new ArrayList<>();
092
093    /** Effective prefetch info. */
094    protected PrefetchInfo prefetchInfo;
095
096    /** Effective schemas. */
097    protected Map<String, Schema> schemas = new HashMap<>();
098
099    protected final Map<String, Schema> uriToSchema = new HashMap<>();
100
101    protected final Map<String, Schema> prefixToSchema = new HashMap<>();
102
103    /** Effective facets. */
104    protected Map<String, CompositeType> facets = new HashMap<>();
105
106    protected Set<String> noPerDocumentQueryFacets = new HashSet<>();
107
108    /** Effective document types. */
109    protected Map<String, DocumentTypeImpl> documentTypes = new HashMap<>();
110
111    protected Map<String, Set<String>> documentTypesExtending = new HashMap<>();
112
113    protected Map<String, Set<String>> documentTypesForFacet = new HashMap<>();
114
115    /** Effective proxy schemas. */
116    protected List<Schema> proxySchemas = new ArrayList<>();
117
118    /** Effective proxy schema names. */
119    protected Set<String> proxySchemaNames = new HashSet<>();
120
121    /** Fields computed lazily. */
122    private Map<String, Field> fields = new ConcurrentHashMap<>();
123
124    private File schemaDir;
125
126    public static final String SCHEMAS_DIR_NAME = "schemas";
127
128    public SchemaManagerImpl() {
129        schemaDir = new File(Environment.getDefault().getTemp(), SCHEMAS_DIR_NAME);
130        schemaDir.mkdirs();
131        clearSchemaDir();
132        registerBuiltinTypes();
133    }
134
135    protected void clearSchemaDir() {
136        try {
137            org.apache.commons.io.FileUtils.cleanDirectory(schemaDir);
138        } catch (IOException e) {
139            throw new RuntimeException(e);
140        }
141    }
142
143    public File getSchemasDir() {
144        return schemaDir;
145    }
146
147    protected void registerBuiltinTypes() {
148        for (Type type : XSDTypes.getTypes()) {
149            registerType(type);
150        }
151        registerType(AnyType.INSTANCE);
152    }
153
154    protected void registerType(Type type) {
155        types.put(type.getName(), type);
156    }
157
158    // called by XSDLoader
159    protected Type getType(String name) {
160        return types.get(name);
161    }
162
163    // for tests
164    protected Collection<Type> getTypes() {
165        return types.values();
166    }
167
168    public synchronized void registerConfiguration(TypeConfiguration config) {
169        allConfigurations.add(config);
170        dirty = true;
171        log.info("Registered global prefetch: " + config.prefetchInfo);
172    }
173
174    public synchronized void unregisterConfiguration(TypeConfiguration config) {
175        if (allConfigurations.remove(config)) {
176            dirty = true;
177            log.info("Unregistered global prefetch: " + config.prefetchInfo);
178        } else {
179            log.error("Unregistering unknown prefetch: " + config.prefetchInfo);
180
181        }
182    }
183
184    public synchronized void registerSchema(SchemaBindingDescriptor sd) {
185        allSchemas.add(sd);
186        dirty = true;
187        log.info("Registered schema: " + sd.name);
188    }
189
190    public synchronized void unregisterSchema(SchemaBindingDescriptor sd) {
191        if (allSchemas.remove(sd)) {
192            dirty = true;
193            log.info("Unregistered schema: " + sd.name);
194        } else {
195            log.error("Unregistering unknown schema: " + sd.name);
196        }
197    }
198
199    public synchronized void registerFacet(FacetDescriptor fd) {
200        allFacets.add(fd);
201        dirty = true;
202        log.info("Registered facet: " + fd.name);
203    }
204
205    public synchronized void unregisterFacet(FacetDescriptor fd) {
206        if (allFacets.remove(fd)) {
207            dirty = true;
208            log.info("Unregistered facet: " + fd.name);
209        } else {
210            log.error("Unregistering unknown facet: " + fd.name);
211        }
212    }
213
214    public synchronized void registerDocumentType(DocumentTypeDescriptor dtd) {
215        allDocumentTypes.add(dtd);
216        dirty = true;
217        log.info("Registered document type: " + dtd.name);
218    }
219
220    public synchronized void unregisterDocumentType(DocumentTypeDescriptor dtd) {
221        if (allDocumentTypes.remove(dtd)) {
222            dirty = true;
223            log.info("Unregistered document type: " + dtd.name);
224        } else {
225            log.error("Unregistering unknown document type: " + dtd.name);
226        }
227    }
228
229    // for tests
230    public DocumentTypeDescriptor getDocumentTypeDescriptor(String name) {
231        DocumentTypeDescriptor last = null;
232        for (DocumentTypeDescriptor dtd : allDocumentTypes) {
233            if (dtd.name.equals(name)) {
234                last = dtd;
235            }
236        }
237        return last;
238    }
239
240    public synchronized void registerProxies(ProxiesDescriptor pd) {
241        allProxies.add(pd);
242        dirty = true;
243        log.info("Registered proxies descriptor for schemas: " + pd.getSchemas());
244    }
245
246    public synchronized void unregisterProxies(ProxiesDescriptor pd) {
247        if (allProxies.remove(pd)) {
248            dirty = true;
249            log.info("Unregistered proxies descriptor for schemas: " + pd.getSchemas());
250        } else {
251            log.error("Unregistering unknown proxies descriptor for schemas: " + pd.getSchemas());
252        }
253    }
254
255    /**
256     * Checks if something has to be recomputed if a dynamic register/unregister happened.
257     */
258    protected void checkDirty() {
259        // variant of double-check idiom
260        if (!dirty) {
261            return;
262        }
263        synchronized (this) {
264            if (!dirty) {
265                return;
266            }
267            // call recompute() synchronized
268            recompute();
269            dirty = false;
270        }
271    }
272
273    /**
274     * Recomputes effective registries for schemas, facets and document types.
275     */
276    protected void recompute() {
277        recomputeConfiguration();
278        recomputeSchemas();
279        recomputeFacets(); // depend on schemas
280        recomputeDocumentTypes(); // depend on schemas and facets
281        recomputeProxies(); // depend on schemas
282        fields.clear(); // re-filled lazily
283    }
284
285    /*
286     * ===== Configuration =====
287     */
288
289    protected void recomputeConfiguration() {
290        if (allConfigurations.isEmpty()) {
291            prefetchInfo = null;
292        } else {
293            TypeConfiguration last = allConfigurations.get(allConfigurations.size() - 1);
294            prefetchInfo = new PrefetchInfo(last.prefetchInfo);
295        }
296    }
297
298    /*
299     * ===== Schemas =====
300     */
301
302    protected void recomputeSchemas() {
303        schemas.clear();
304        uriToSchema.clear();
305        prefixToSchema.clear();
306        RuntimeException errors = new RuntimeException("Cannot load schemas");
307        // on reload, don't take confuse already-copied schemas with those contributed
308        clearSchemaDir();
309        // resolve which schemas to actually load depending on overrides
310        Map<String, SchemaBindingDescriptor> resolvedSchemas = new LinkedHashMap<>();
311        for (SchemaBindingDescriptor sd : allSchemas) {
312            String name = sd.name;
313            if (resolvedSchemas.containsKey(name)) {
314                if (!sd.override) {
315                    log.warn("Schema " + name + " is redefined but will not be overridden");
316                    continue;
317                }
318                log.debug("Reregistering schema: " + name + " from " + sd.file);
319            } else {
320                log.debug("Registering schema: " + name + " from " + sd.file);
321            }
322            resolvedSchemas.put(name, sd);
323        }
324        for (SchemaBindingDescriptor sd : resolvedSchemas.values()) {
325            try {
326                copySchema(sd);
327            } catch (IOException error) {
328                errors.addSuppressed(error);
329            }
330        }
331        for (SchemaBindingDescriptor sd : resolvedSchemas.values()) {
332            try {
333                loadSchema(sd);
334            } catch (IOException | SAXException | TypeException error) {
335                errors.addSuppressed(error);
336            }
337        }
338        if (errors.getSuppressed().length > 0) {
339            throw errors;
340        }
341    }
342
343    protected void copySchema(SchemaBindingDescriptor sd) throws IOException {
344        if (sd.src == null || sd.src.length() == 0) {
345            // log.error("INLINE Schemas ARE NOT YET IMPLEMENTED!");
346            return;
347        }
348        URL url = sd.context.getLocalResource(sd.src);
349        if (url == null) {
350            // try asking the class loader
351            url = sd.context.getResource(sd.src);
352        }
353        if (url == null) {
354            log.error("XSD Schema not found: " + sd.src);
355            return;
356        }
357        InputStream in = url.openStream();
358        try {
359            sd.file = new File(schemaDir, sd.name + ".xsd");
360            FileUtils.copyToFile(in, sd.file); // may overwrite
361        } finally {
362            in.close();
363        }
364    }
365
366    protected void loadSchema(SchemaBindingDescriptor sd) throws IOException, SAXException, TypeException {
367        if (sd.file == null) {
368            // log.error("INLINE Schemas ARE NOT YET IMPLEMENTED!");
369            return;
370        }
371        // loadSchema calls this.registerSchema
372        XSDLoader schemaLoader = new XSDLoader(this, sd);
373        schemaLoader.loadSchema(sd.name, sd.prefix, sd.file, sd.xsdRootElement);
374        log.info("Registered schema: " + sd.name + " from " + sd.file);
375    }
376
377    // called from XSDLoader
378    protected void registerSchema(Schema schema) {
379        schemas.put(schema.getName(), schema);
380        Namespace ns = schema.getNamespace();
381        uriToSchema.put(ns.uri, schema);
382        if (!StringUtils.isBlank(ns.prefix)) {
383            prefixToSchema.put(ns.prefix, schema);
384        }
385    }
386
387    @Override
388    public Schema[] getSchemas() {
389        checkDirty();
390        return new ArrayList<>(schemas.values()).toArray(new Schema[0]);
391    }
392
393    @Override
394    public Schema getSchema(String name) {
395        checkDirty();
396        return schemas.get(name);
397    }
398
399    @Override
400    public Schema getSchemaFromPrefix(String schemaPrefix) {
401        checkDirty();
402        return prefixToSchema.get(schemaPrefix);
403    }
404
405    @Override
406    public Schema getSchemaFromURI(String schemaURI) {
407        checkDirty();
408        return uriToSchema.get(schemaURI);
409    }
410
411    /*
412     * ===== Facets =====
413     */
414
415    protected void recomputeFacets() {
416        facets.clear();
417        noPerDocumentQueryFacets.clear();
418        for (FacetDescriptor fd : allFacets) {
419            recomputeFacet(fd);
420        }
421    }
422
423    protected void recomputeFacet(FacetDescriptor fd) {
424        Set<String> fdSchemas = SchemaDescriptor.getSchemaNames(fd.schemas);
425        registerFacet(fd.name, fdSchemas);
426        if (Boolean.FALSE.equals(fd.perDocumentQuery)) {
427            noPerDocumentQueryFacets.add(fd.name);
428        }
429    }
430
431    // also called when a document type references an unknown facet (WARN)
432    protected CompositeType registerFacet(String name, Set<String> schemaNames) {
433        List<Schema> facetSchemas = new ArrayList<>(schemaNames.size());
434        for (String schemaName : schemaNames) {
435            Schema schema = schemas.get(schemaName);
436            if (schema == null) {
437                log.error("Facet: " + name + " uses unknown schema: " + schemaName);
438                continue;
439            }
440            facetSchemas.add(schema);
441        }
442        CompositeType ct = new CompositeTypeImpl(null, SchemaNames.FACETS, name, facetSchemas);
443        facets.put(name, ct);
444        return ct;
445    }
446
447    @Override
448    public CompositeType[] getFacets() {
449        checkDirty();
450        return new ArrayList<>(facets.values()).toArray(new CompositeType[facets.size()]);
451    }
452
453    @Override
454    public CompositeType getFacet(String name) {
455        checkDirty();
456        return facets.get(name);
457    }
458
459    @Override
460    public Set<String> getNoPerDocumentQueryFacets() {
461        checkDirty();
462        return Collections.unmodifiableSet(noPerDocumentQueryFacets);
463    }
464
465    /*
466     * ===== Document types =====
467     */
468
469    protected void recomputeDocumentTypes() {
470        // effective descriptors with override
471        // linked hash map to keep order for reproducibility
472        Map<String, DocumentTypeDescriptor> dtds = new LinkedHashMap<>();
473        for (DocumentTypeDescriptor dtd : allDocumentTypes) {
474            String name = dtd.name;
475            DocumentTypeDescriptor newDtd = dtd;
476            if (dtd.append && dtds.containsKey(dtd.name)) {
477                newDtd = mergeDocumentTypeDescriptors(dtd, dtds.get(name));
478            }
479            dtds.put(name, newDtd);
480        }
481        // recompute all types, parents first
482        documentTypes.clear();
483        documentTypesExtending.clear();
484        registerDocumentType(new DocumentTypeImpl(TypeConstants.DOCUMENT)); // Document
485        for (String name : dtds.keySet()) {
486            LinkedHashSet<String> stack = new LinkedHashSet<>();
487            recomputeDocumentType(name, stack, dtds);
488        }
489
490        // document types having a given facet
491        documentTypesForFacet.clear();
492        for (DocumentType docType : documentTypes.values()) {
493            for (String facet : docType.getFacets()) {
494                Set<String> set = documentTypesForFacet.get(facet);
495                if (set == null) {
496                    documentTypesForFacet.put(facet, set = new HashSet<>());
497                }
498                set.add(docType.getName());
499            }
500        }
501
502    }
503
504    protected DocumentTypeDescriptor mergeDocumentTypeDescriptors(DocumentTypeDescriptor src, DocumentTypeDescriptor dst) {
505        return dst.clone().merge(src);
506    }
507
508    protected DocumentType recomputeDocumentType(String name, Set<String> stack,
509            Map<String, DocumentTypeDescriptor> dtds) {
510        DocumentTypeImpl docType = documentTypes.get(name);
511        if (docType != null) {
512            // already done
513            return docType;
514        }
515        if (stack.contains(name)) {
516            log.error("Document type: " + name + " used in parent inheritance loop: " + stack);
517            return null;
518        }
519        DocumentTypeDescriptor dtd = dtds.get(name);
520        if (dtd == null) {
521            log.error("Document type: " + name + " does not exist, used as parent by type: " + stack);
522            return null;
523        }
524
525        // find and recompute the parent first
526        DocumentType parent;
527        String parentName = dtd.superTypeName;
528        if (parentName == null) {
529            parent = null;
530        } else {
531            parent = documentTypes.get(parentName);
532            if (parent == null) {
533                stack.add(name);
534                parent = recomputeDocumentType(parentName, stack, dtds);
535                stack.remove(name);
536            }
537        }
538
539        // what it extends
540        for (Type p = parent; p != null; p = p.getSuperType()) {
541            Set<String> set = documentTypesExtending.get(p.getName());
542            set.add(name);
543        }
544
545        return recomputeDocumentType(name, dtd, parent);
546    }
547
548    protected DocumentType recomputeDocumentType(String name, DocumentTypeDescriptor dtd, DocumentType parent) {
549        // find the facets and schemas names
550        Set<String> facetNames = new HashSet<>();
551        Set<String> schemaNames = SchemaDescriptor.getSchemaNames(dtd.schemas);
552        facetNames.addAll(Arrays.asList(dtd.facets));
553
554        // inherited
555        if (parent != null) {
556            facetNames.addAll(parent.getFacets());
557            schemaNames.addAll(Arrays.asList(parent.getSchemaNames()));
558        }
559
560        // add schemas names from facets
561        for (String facetName : facetNames) {
562            CompositeType ct = facets.get(facetName);
563            if (ct == null) {
564                log.warn("Undeclared facet: " + facetName + " used in document type: " + name);
565                // register it with no schemas
566                ct = registerFacet(facetName, Collections.<String> emptySet());
567            }
568            schemaNames.addAll(Arrays.asList(ct.getSchemaNames()));
569        }
570
571        // find the schemas
572        List<Schema> docTypeSchemas = new ArrayList<>();
573        for (String schemaName : schemaNames) {
574            Schema schema = schemas.get(schemaName);
575            if (schema == null) {
576                log.error("Document type: " + name + " uses unknown schema: " + schemaName);
577                continue;
578            }
579            docTypeSchemas.add(schema);
580        }
581
582        // create doctype
583        PrefetchInfo prefetch = dtd.prefetch == null ? prefetchInfo : new PrefetchInfo(dtd.prefetch);
584        DocumentTypeImpl docType = new DocumentTypeImpl(name, parent, docTypeSchemas, facetNames, prefetch);
585        registerDocumentType(docType);
586
587        return docType;
588    }
589
590    protected void registerDocumentType(DocumentTypeImpl docType) {
591        String name = docType.getName();
592        documentTypes.put(name, docType);
593        documentTypesExtending.put(name, new HashSet<>(Collections.singleton(name)));
594    }
595
596    @Override
597    public DocumentType getDocumentType(String name) {
598        checkDirty();
599        return documentTypes.get(name);
600    }
601
602    @Override
603    public Set<String> getDocumentTypeNamesForFacet(String facet) {
604        checkDirty();
605        return documentTypesForFacet.get(facet);
606    }
607
608    @Override
609    public Set<String> getDocumentTypeNamesExtending(String docTypeName) {
610        checkDirty();
611        return documentTypesExtending.get(docTypeName);
612    }
613
614    @Override
615    public DocumentType[] getDocumentTypes() {
616        checkDirty();
617        return new ArrayList<DocumentType>(documentTypes.values()).toArray(new DocumentType[0]);
618    }
619
620    @Override
621    public int getDocumentTypesCount() {
622        checkDirty();
623        return documentTypes.size();
624    }
625
626    @Override
627    public boolean hasSuperType(String docType, String superType) {
628        if (docType == null || superType == null) {
629            return false;
630        }
631        Set<String> subTypes = getDocumentTypeNamesExtending(superType);
632        return subTypes != null && subTypes.contains(docType);
633    }
634
635    /*
636     * ===== Proxies =====
637     */
638
639    protected void recomputeProxies() {
640        List<Schema> list = new ArrayList<>();
641        Set<String> nameSet = new HashSet<>();
642        for (ProxiesDescriptor pd : allProxies) {
643            if (!pd.getType().equals("*")) {
644                log.error("Proxy descriptor for specific type not supported: " + pd);
645            }
646            for (String schemaName : pd.getSchemas()) {
647                if (nameSet.contains(schemaName)) {
648                    continue;
649                }
650                Schema schema = schemas.get(schemaName);
651                if (schema == null) {
652                    log.error("Proxy schema uses unknown schema: " + schemaName);
653                    continue;
654                }
655                list.add(schema);
656                nameSet.add(schemaName);
657            }
658        }
659        proxySchemas = list;
660        proxySchemaNames = nameSet;
661    }
662
663    @Override
664    public List<Schema> getProxySchemas(String docType) {
665        // docType unused for now
666        checkDirty();
667        return new ArrayList<>(proxySchemas);
668    }
669
670    @Override
671    public boolean isProxySchema(String schema, String docType) {
672        // docType unused for now
673        checkDirty();
674        return proxySchemaNames.contains(schema);
675    }
676
677    /*
678     * ===== Fields =====
679     */
680
681    @Override
682    public Field getField(String xpath) {
683        checkDirty();
684        Field field = null;
685        if (xpath != null && xpath.contains("/")) {
686            // need to resolve subfields
687            String[] properties = xpath.split("/");
688            Field resolvedField = getField(properties[0]);
689            for (int x = 1; x < properties.length; x++) {
690                if (resolvedField == null) {
691                    break;
692                }
693                resolvedField = getField(resolvedField, properties[x], x == properties.length - 1);
694            }
695            if (resolvedField != null) {
696                field = resolvedField;
697            }
698        } else {
699            field = fields.get(xpath);
700            if (field == null) {
701                QName qname = QName.valueOf(xpath);
702                String prefix = qname.getPrefix();
703                Schema schema = getSchemaFromPrefix(prefix);
704                if (schema == null) {
705                    // try using the name
706                    schema = getSchema(prefix);
707                }
708                if (schema != null) {
709                    field = schema.getField(qname.getLocalName());
710                    if (field != null) {
711                        // map is concurrent so parallelism is ok
712                        fields.put(xpath, field);
713                    }
714                }
715            }
716        }
717        return field;
718    }
719
720    @Override
721    public Field getField(Field parent, String subFieldName) {
722        return getField(parent, subFieldName, true);
723    }
724
725    protected Field getField(Field parent, String subFieldName, boolean finalCall) {
726        if (parent != null) {
727            Type type = parent.getType();
728            if (type.isListType()) {
729                ListType listType = (ListType) type;
730                // remove indexes in case of multiple values
731                if ("*".equals(subFieldName)) {
732                    if (!finalCall) {
733                        return parent;
734                    } else {
735                        return resolveSubField(listType, null, true);
736                    }
737                }
738                try {
739                    Integer.valueOf(subFieldName);
740                    if (!finalCall) {
741                        return parent;
742                    } else {
743                        return resolveSubField(listType, null, true);
744                    }
745                } catch (NumberFormatException e) {
746                    return resolveSubField(listType, subFieldName, false);
747                }
748            } else if (type.isComplexType()) {
749                return ((ComplexType) type).getField(subFieldName);
750            }
751        }
752        return null;
753    }
754
755    protected Field resolveSubField(ListType listType, String subName, boolean fallbackOnSubElement) {
756        Type itemType = listType.getFieldType();
757        if (itemType.isComplexType() && subName != null) {
758            ComplexType complexType = (ComplexType) itemType;
759            Field subField = complexType.getField(subName);
760            return subField;
761        }
762        if (fallbackOnSubElement) {
763            return listType.getField();
764        }
765        return null;
766    }
767
768    public void flushPendingsRegistration() {
769        checkDirty();
770    }
771
772}