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