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