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