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