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