/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bogdan Stefanescu * Wojciech Sulejman * Florent Guillaume */ package org.eclipse.ecr.core.schema; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.ecr.core.schema.types.ComplexType; import org.eclipse.ecr.core.schema.types.ComplexTypeImpl; import org.eclipse.ecr.core.schema.types.Constraint; import org.eclipse.ecr.core.schema.types.Field; import org.eclipse.ecr.core.schema.types.ListType; import org.eclipse.ecr.core.schema.types.ListTypeImpl; import org.eclipse.ecr.core.schema.types.Schema; import org.eclipse.ecr.core.schema.types.SchemaImpl; import org.eclipse.ecr.core.schema.types.SimpleType; import org.eclipse.ecr.core.schema.types.SimpleTypeImpl; import org.eclipse.ecr.core.schema.types.Type; import org.eclipse.ecr.core.schema.types.TypeBindingException; import org.eclipse.ecr.core.schema.types.TypeException; import org.eclipse.ecr.core.schema.types.constraints.StringLengthConstraint; import org.xml.sax.EntityResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.sun.xml.xsom.XSAttributeDecl; import com.sun.xml.xsom.XSAttributeUse; import com.sun.xml.xsom.XSComplexType; import com.sun.xml.xsom.XSContentType; import com.sun.xml.xsom.XSElementDecl; import com.sun.xml.xsom.XSFacet; import com.sun.xml.xsom.XSListSimpleType; import com.sun.xml.xsom.XSModelGroup; import com.sun.xml.xsom.XSParticle; import com.sun.xml.xsom.XSSchema; import com.sun.xml.xsom.XSSchemaSet; import com.sun.xml.xsom.XSTerm; import com.sun.xml.xsom.XSType; import com.sun.xml.xsom.XmlString; import com.sun.xml.xsom.impl.RestrictionSimpleTypeImpl; import com.sun.xml.xsom.parser.XSOMParser; /** * Loader of XSD schemas into Nuxeo Schema objects. */ public class XSDLoader { public static final String NS_XSD = "http://www.w3.org/2001/XMLSchema"; private static final Log log = LogFactory.getLog(XSDLoader.class); private final SchemaManagerImpl typeManager; private XSOMParser parser; public XSDLoader(SchemaManagerImpl typeManager) { this.typeManager = typeManager; //initParser(); // TODO: all schemas are collected in the schema set when reusing the parser } protected void initParser() { parser = new XSOMParser(); ErrorHandler errorHandler = new SchemaErrorHandler(); parser.setErrorHandler(errorHandler); parser.setEntityResolver(new CustomEntityResolver()); } // TODO: this type of loading schemas must use a new parser each time // a new schema should be loaded. // When reusing the parser the SchemaSet is collecting all the schemas. public static XSSchema getUserSchema(XSSchemaSet schemaSet) { Collection<XSSchema> schemas = schemaSet.getSchemas(); for (XSSchema schema : schemas) { String ns = schema.getTargetNamespace(); if (ns.length() > 0 && !ns.equals(NS_XSD)) { return schema; } } return null; } public Schema loadSchema(String name, String prefix, File file, boolean override) throws SAXException, IOException, TypeException { initParser(); // TODO: after fixing schema loading remove this and put it in the ctor // since we may improve schema loading speed by reusing already parsed schemas String systemId = file.toURI().toURL().toExternalForm(); if (file.getPath().startsWith("\\\\")) { // Windows UNC share // work around a bug in Xerces due to // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5086147 // (xsom passes a systemId of the form file://server/share/... // but this is not parsed correctly when turned back into // a File object inside Xerces) systemId = systemId.replace("file://", "file:////"); } parser.parse(systemId); XSSchemaSet schemaSet = parser.getResult(); if (schemaSet != null) { XSSchema schema = getUserSchema(schemaSet); if (schema != null) { return loadSchema(name, prefix, schema, override); } } return null; } public Schema loadSchema(String name, String prefix, URL url) throws SAXException, TypeException { initParser(); // TODO: after fixing schema loading remove this and put it in the ctor parser.parse(url); XSSchemaSet schemaSet = parser.getResult(); if (schemaSet != null) { XSSchema schema = getUserSchema(schemaSet); if (schema != null) { return loadSchema(name, prefix, schema, false); } } return null; } public Schema loadSchema(String name, String prefix, InputStream in) throws SAXException, TypeException { initParser(); // TODO: after fixing schema loading remove this and put it in the ctor parser.parse(in); XSSchemaSet schemaSet = parser.getResult(); if (schemaSet != null) { XSSchema schema = getUserSchema(schemaSet); if (schema != null) { return loadSchema(name, prefix, schema, false); } } return null; } public Schema loadSchema(String name, String prefix, XSSchema schema, boolean override) throws TypeException { String ns = schema.getTargetNamespace(); try { Schema ecmSchema = typeManager.getSchema(name); if (ecmSchema != null) { // schema already defined log.warn("Schema " + ns + " is already registered"); if (!override) { return ecmSchema; } } ecmSchema = new SchemaImpl(name, new Namespace(ns, prefix)); // load elements Collection<XSElementDecl> elements = schema.getElementDecls().values(); for (XSElementDecl el : elements) { // register the type if not yet registered Type ecmType = loadType(ecmSchema, el.getType()); if (ecmType != null) { // add the field to the schema createField(ecmSchema, el, ecmType); } else { log.warn("Failed to load field " + el.getName() + " : " + el.getType()); } } typeManager.registerSchema(ecmSchema); return ecmSchema; } catch (TypeBindingException e) { throw e; } catch (Throwable t) { throw new TypeException("Failed to load XSD schema " + ns, t); } } public Type loadType(Schema schema, XSType type) throws TypeBindingException { String name; if (type.getName() == null || type.isLocal()) { name = getAnonymousTypeName(type); if (name == null) { log.warn("Unable to load type - no name found"); return null; } } else { name = type.getName(); } Type ecmType = typeManager.getType(name); // look into global types if (ecmType != null) { // an already registered type return ecmType; } // look into user types ecmType = schema.getType(name); if (ecmType != null) { // an already registered type return ecmType; } // TODO!!!!!!! if (type.getTargetNamespace().equals(NS_XSD)) { ecmType = XSDTypes.getType(name); typeManager.registerType(ecmType); return ecmType; // register the primitive type } else if (type.isSimpleType()) { if (type instanceof XSListSimpleType) { ecmType = loadListType(schema, (XSListSimpleType) type); } else { ecmType = loadSimpleType(schema, type); } } else { ecmType = loadComplexType(schema, name, type.asComplexType()); } if (ecmType != null) { schema.registerType(ecmType); } return ecmType; } public Type loadLocalType(XSType xsType) { // TODO return null; } /** * * @param name the type name (not theat type may have a null name if an anonymous type) * @param type * @return * @throws TypeBindingException */ private Type loadComplexType(Schema schema, String name, XSType type) throws TypeBindingException { //String name = type.getName(); XSType baseType = type.getBaseType(); ComplexType superType = null; // the anyType is the basetype of itself if (baseType.getBaseType() != baseType) { // have a base type if (baseType.isComplexType()) { superType = (ComplexType) loadType(schema, baseType); } else { log.warn("Complex type has a non complex type super type???"); } } XSComplexType xsct = type.asComplexType(); // try to get the delta content XSContentType content = xsct.getExplicitContent(); // if none get the entire content if (content == null) { content = xsct.getContentType(); } Type ret = createComplexType(schema, superType, name, content); if (ret instanceof ComplexType) { // load attributes if any loadAttributes(schema, xsct, (ComplexType) ret); } return ret; } private void loadAttributes(Schema schema, XSComplexType xsct, ComplexType ct) throws TypeBindingException { Collection<? extends XSAttributeUse> attrs = xsct.getAttributeUses(); for (XSAttributeUse attr : attrs) { XSAttributeDecl at = attr.getDecl(); Type fieldType = loadType(schema, at.getType()); if (fieldType == null) { throw new TypeBindingException("Cannot add type for '" + at.getName() + "'"); } createField(ct, at, fieldType); } } private SimpleType loadSimpleType(Schema schema, XSType type) throws TypeBindingException { String name = type.getName(); if (name == null) { // probably a local type -> ignore it return null; } XSType baseType = type.getBaseType(); SimpleType superType = null; if (baseType != type) { // have a base type superType = (SimpleType) loadType(schema, baseType); } SimpleTypeImpl simpleType = new SimpleTypeImpl(superType, schema.getName(), name); // add constraints/restrictions to the simple type if (type instanceof RestrictionSimpleTypeImpl) { RestrictionSimpleTypeImpl restrictionType = (RestrictionSimpleTypeImpl) type; List<Constraint> constraints = new ArrayList<Constraint>(1); XSFacet maxLength = restrictionType.getFacet("maxLength"); if (maxLength != null) { int min = 0; // for now int max = Integer.parseInt(maxLength.getValue().toString()); Constraint constraint = new StringLengthConstraint(min, max); constraints.add(constraint); } simpleType.setConstraints(constraints.toArray(new Constraint[0])); } return simpleType; } private ListType loadListType(Schema schema, XSListSimpleType type) { String name = type.getName(); if (name == null) { // probably a local type -> ignore it return null; } XSType xsItemType = type.getItemType(); Type itemType; if (xsItemType.getTargetNamespace().equals(NS_XSD)) { itemType = XSDTypes.getType(xsItemType.getName()); } else { //itemType = loadType(schema, type); //TODO: type must be already defined - use a dependency manager or something to // support types that are not yet defined itemType = typeManager.getType(xsItemType.getName()); } if (itemType == null) { log.error("list item type was not defined -> you should define first the item type"); return null; } return new ListTypeImpl(schema.getName(), name, itemType); } private Type createComplexType(Schema schema, ComplexType superType, String name, XSContentType content) throws TypeBindingException { //System.out.println("DEBUG > defining complex type: " + name); ComplexType ct = new ComplexTypeImpl(superType, schema.getName(), name); // -------- Workaround - we register now the complex type - to fix recursive references to the same type schema.registerType(ct); // ------------------------------------------ XSParticle particle = content.asParticle(); if (particle == null) { // complex type without particle -> may be it contains only attributes -> return it as is return ct; } XSTerm term = particle.getTerm(); XSModelGroup mg = term.asModelGroup(); if (mg == null) { // TODO don't know how to handle this for now throw new TypeBindingException("unsupported complex type"); } XSParticle[] group = mg.getChildren(); if (group.length == 1 && superType == null && group[0].isRepeated()) { // a list return createListType(schema, name, group[0]); } for (XSParticle child : group) { term = child.getTerm(); XSElementDecl element = term.asElementDecl(); if (element == null) { // TODO don't know how to handle this for now log.warn("Ignoring " + name + " unsupported complex type"); return null; } loadComplexTypeElement(schema, ct, element); } return ct; } public ListType createListType(Schema schema, String name, XSParticle particle) throws TypeBindingException { XSElementDecl element = particle.getTerm().asElementDecl(); if (element == null) { log.warn("Ignoring " + name + " unsupported list type"); return null; } XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } Type type = loadType(schema, element.getType()); return new ListTypeImpl(schema.getName(), name, type, element.getName(), defValue, particle.getMinOccurs(), particle.getMaxOccurs()); } private void loadComplexTypeElement(Schema schema, ComplexType type, XSElementDecl element) throws TypeBindingException { XSType elementType = element.getType(); Type fieldType = loadType(schema, elementType); if (fieldType != null) { createField(type , element, fieldType); } } private static Field createField(ComplexType type, XSElementDecl element, Type fieldType) { String elementName = element.getName(); XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } int flags = 0; if (defValue == null) { dv = element.getFixedValue(); if (dv != null) { defValue = dv.value; flags |= Field.CONSTANT; } } if (element.isNillable()) { flags |= Field.NILLABLE; } Field field = type.addField(elementName, fieldType.getRef(), defValue, flags); //set the max field length from the constraints if (fieldType instanceof SimpleTypeImpl) { for (Constraint constraint : ((SimpleTypeImpl) fieldType).getConstraints()) { if (constraint instanceof StringLengthConstraint) { StringLengthConstraint slc = (StringLengthConstraint) constraint; field.setMaxLength(slc.getMax()); } } } return field; } private static Field createField(ComplexType type, XSAttributeDecl element, Type fieldType) { String elementName = element.getName(); XmlString dv = element.getDefaultValue(); String defValue = null; if (dv != null) { defValue = dv.value; } int flags = 0; if (defValue == null) { dv = element.getFixedValue(); if (dv != null) { defValue = dv.value; flags |= Field.CONSTANT; } } return type.addField(elementName, fieldType.getRef(), defValue, flags); } static class SchemaErrorHandler implements ErrorHandler { @Override public void error(SAXParseException exception) throws SAXException { log.error("Error: " + exception.getMessage()); throw exception; } @Override public void fatalError(SAXParseException exception) throws SAXException { log.error("FatalError: " + exception.getMessage()); throw exception; } @Override public void warning(SAXParseException exception) throws SAXException { log.error("Warning: " + exception.getMessage()); } } class CustomEntityResolver implements EntityResolver { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if (systemId != null) { URL url = typeManager.resolveSchemaLocation(systemId); if (url != null) { InputSource is = new InputSource(url.openStream()); is.setPublicId(publicId); return is; } } return null; } } private static String getAnonymousTypeName(XSType type) { if (type.isComplexType()) { XSElementDecl container = type.asComplexType().getScope(); String elName = container.getName(); return elName + "#anonymousType"; } return null; } }