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