/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.io.xsd.reader.internal; import java.math.BigDecimal; import java.net.URI; import java.text.MessageFormat; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.QName; import org.apache.ws.commons.schema.XmlSchemaEnumerationFacet; import org.apache.ws.commons.schema.XmlSchemaFacet; import org.apache.ws.commons.schema.XmlSchemaFractionDigitsFacet; import org.apache.ws.commons.schema.XmlSchemaLengthFacet; import org.apache.ws.commons.schema.XmlSchemaMaxExclusiveFacet; import org.apache.ws.commons.schema.XmlSchemaMaxInclusiveFacet; import org.apache.ws.commons.schema.XmlSchemaMaxLengthFacet; import org.apache.ws.commons.schema.XmlSchemaMinExclusiveFacet; import org.apache.ws.commons.schema.XmlSchemaMinInclusiveFacet; import org.apache.ws.commons.schema.XmlSchemaMinLengthFacet; import org.apache.ws.commons.schema.XmlSchemaObject; import org.apache.ws.commons.schema.XmlSchemaObjectCollection; import org.apache.ws.commons.schema.XmlSchemaPatternFacet; import org.apache.ws.commons.schema.XmlSchemaSimpleType; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeContent; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeList; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeRestriction; import org.apache.ws.commons.schema.XmlSchemaSimpleTypeUnion; import org.apache.ws.commons.schema.XmlSchemaTotalDigitsFacet; import org.apache.ws.commons.schema.XmlSchemaWhiteSpaceFacet; import org.geotools.feature.NameImpl; import org.geotools.feature.type.AttributeTypeImpl; import org.geotools.xs.XSSchema; import org.opengis.feature.type.AttributeType; import org.opengis.feature.type.Name; import eu.esdihumboldt.hale.common.core.io.report.IOReporter; import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl; import eu.esdihumboldt.hale.common.schema.model.TypeConstraint; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.constraint.type.AbstractFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.AugmentedValueFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.Binding; import eu.esdihumboldt.hale.common.schema.model.constraint.type.ElementType; import eu.esdihumboldt.hale.common.schema.model.constraint.type.Enumeration; import eu.esdihumboldt.hale.common.schema.model.constraint.type.HasValueFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.MappableFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.MappingRelevantFlag; import eu.esdihumboldt.hale.common.schema.model.constraint.type.ValidationConstraint; import eu.esdihumboldt.hale.io.gml.geometry.Geometries; import eu.esdihumboldt.hale.io.gml.geometry.GeometryNotSupportedException; import eu.esdihumboldt.hale.io.xsd.constraint.RestrictionFlag; import eu.esdihumboldt.hale.io.xsd.model.XmlIndex; import eu.esdihumboldt.hale.io.xsd.reader.XmlSchemaReader; import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.SkipGeometryValidation; import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.UnionBinding; import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.UnionEnumeration; import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.UnionValidationConstraint; import eu.esdihumboldt.util.validator.AndValidator; import eu.esdihumboldt.util.validator.DigitCountValidator; import eu.esdihumboldt.util.validator.EnumerationValidator; import eu.esdihumboldt.util.validator.LengthValidator; import eu.esdihumboldt.util.validator.NumberValidator; import eu.esdihumboldt.util.validator.OrValidator; import eu.esdihumboldt.util.validator.PatternValidator; import eu.esdihumboldt.util.validator.Validator; /** * Utility methods regarding type resolving * * @author Simon Templer * @partner 01 / Fraunhofer Institute for Computer Graphics Research */ public abstract class XmlTypeUtil { // private static final ALogger log = ALoggerFactory.getLogger(TypeUtil.class); // // private static final AGroup TYPE_RESOLVE = AGroupFactory.getGroup(Messages.getString("TypeUtil.0")); //$NON-NLS-1$ /** * The XML simple types schema */ protected static final XSSchema xsSchema = new XSSchema(); /** * GML 3.2 namespace */ private static final String NAMESPACE_GML32 = "http://www.opengis.net/gml/3.2"; //$NON-NLS-1$ /** * GML up to 3.1.x namespace */ private static final String NAMESPACE_GML = "http://www.opengis.net/gml"; //$NON-NLS-1$ /** * Qualified name of the anyType schema type */ public static final QName NAME_ANY_TYPE = new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI, "anyType"); /** * Set of XML schema types that should get a String binding but don't get * one through the Geotools bindings * * @see "http://www.w3schools.com/Schema/schema_dtypes_string.asp" */ private static final Set<String> XS_STRING_TYPES = new HashSet<String>(); static { XS_STRING_TYPES.add("ID"); //$NON-NLS-1$ XS_STRING_TYPES.add("IDREF"); //$NON-NLS-1$ XS_STRING_TYPES.add("NCName"); //$NON-NLS-1$ XS_STRING_TYPES.add("token"); //$NON-NLS-1$ XS_STRING_TYPES.add("Name"); //$NON-NLS-1$ XS_STRING_TYPES.add("language"); //$NON-NLS-1$ XS_STRING_TYPES.add("ENTITY"); //$NON-NLS-1$ XS_STRING_TYPES.add("ENTITIES"); //$NON-NLS-1$ XS_STRING_TYPES.add("NMTOKEN"); //$NON-NLS-1$ XS_STRING_TYPES.add("NMTOKENS"); //$NON-NLS-1$ XS_STRING_TYPES.add("normalizedString"); //$NON-NLS-1$ XS_STRING_TYPES.add("QName"); //$NON-NLS-1$ } private static final Set<QName> GML_GEOMETRY_TYPES = new HashSet<QName>(); static { GML_GEOMETRY_TYPES.add(new QName(NAMESPACE_GML, "AbstractGeometryType")); GML_GEOMETRY_TYPES.add(new QName(NAMESPACE_GML32, "AbstractGeometryType")); } // /** // * Get the attribute type for an GML type // * // * @param typeName the type name // * // * @return the attribute type or <code>null</code> // */ // public static AttributeType getGMLAttributeType(Name typeName) { // AttributeType gmlType = gml3Schema.get(typeName); // if (gmlType == null && typeName.getNamespaceURI().equals(NAMESPACE_GML3_2)) { // // try again with GML2/3 namespace // gmlType = gml3Schema.get(new NameImpl(NAMESPACE_GML, typeName.getLocalPart())); // //FIXME replicate type with correct namespace? // } // return gmlType; // } // // /** // * Get the predefined attribute type (GML or XS) with the given type name // * // * @param typeName the type name // * // * @return the attribute type or <code>null</code> // */ // public static AttributeType getPredefinedAttributeType(Name typeName) { // AttributeType result = xsSchema.get(typeName); // // if (result == null) { // result = getGMLAttributeType(typeName); // } // // return result; // } /** * Configure a type with defaults if possible, e.g. for simple types * * @param type the type to configure */ public static void configureType(XmlTypeDefinition type) { // XSD simple types if (configureXsdSimpleType(type)) { return; } // TODO more configuration options? // TODO e.g. GML? } /** * Configure the given type as XML schema simple type if possible * * @param type the type to configure * @return if the type could be configured as XSD simple type */ @SuppressWarnings("unchecked") private static boolean configureXsdSimpleType(XmlTypeDefinition type) { Name typeName = new NameImpl(type.getName().getNamespaceURI(), type.getName() .getLocalPart()); AttributeType ty = xsSchema.get(typeName); // special case: ID etc. - assure String binding if (ty != null && XS_STRING_TYPES.contains(typeName.getLocalPart())) { ty = new AttributeTypeImpl(typeName, java.lang.String.class, false, false, Collections.EMPTY_LIST, ty.getSuper(), null); } // only enable hasValue if the type is not anyType // anyType has special treatment in // XmlSchemaReader.setMetadataAndConstraints(TypeDefinition, ...) boolean hasValue = !typeName.getLocalPart().equals("anyType"); if (ty != null) { // configure type // set binding type.setConstraint(Binding.get(ty.getBinding())); // simple type flag if (hasValue) { type.setConstraint(HasValueFlag.ENABLED); } // not abstract type.setConstraint(AbstractFlag.DISABLED); // not mappable type.setConstraint(MappingRelevantFlag.DISABLED); type.setConstraint(MappableFlag.DISABLED); type.setLocation(URI.create(XMLConstants.W3C_XML_SCHEMA_NS_URI)); if (ty.getDescription() != null) { type.setDescription(ty.getDescription().toString()); } return true; } else { return false; } } /** * Configure a type definition for a simple type based on the * {@link XmlSchemaSimpleType}. * * @param type the type definition * @param simpleType the schema simple type * @param index the XML index for resolving type definitions * @param reporter the report */ public static void configureSimpleType(XmlTypeDefinition type, XmlSchemaSimpleType simpleType, XmlIndex index, IOReporter reporter) { XmlSchemaSimpleTypeContent content = simpleType.getContent(); // it's a simple type type.setConstraint(HasValueFlag.ENABLED); if (content instanceof XmlSchemaSimpleTypeUnion) { // simple type union configureSimpleTypeUnion(type, (XmlSchemaSimpleTypeUnion) content, index, reporter); } else if (content instanceof XmlSchemaSimpleTypeList) { // simple type list configureSimpleTypeList(type, (XmlSchemaSimpleTypeList) content, index, reporter); } else if (content instanceof XmlSchemaSimpleTypeRestriction) { // simple type restriction configureSimpleTypeRestriction(type, (XmlSchemaSimpleTypeRestriction) content, index, reporter); } else { reporter.error(new IOMessageImpl(MessageFormat.format("Unrecognized simple type {0}", type.getName()), null, simpleType.getLineNumber(), simpleType.getLinePosition())); } } /** * Configure a type definition for a simple type based on a simple type * restriction. * * @param type the type definition * @param restriction the simple type restriction * @param index the XML index for resolving type definitions * @param reporter the report */ private static void configureSimpleTypeRestriction(XmlTypeDefinition type, XmlSchemaSimpleTypeRestriction restriction, XmlIndex index, IOReporter reporter) { QName baseTypeName = restriction.getBaseTypeName(); XmlTypeDefinition baseTypeDef; if (baseTypeName != null) { // resolve super type baseTypeDef = index.getOrCreateType(baseTypeName); } else if (restriction.getBaseType() != null) { // simple schema type XmlSchemaSimpleType simpleType = restriction.getBaseType(); // create an anonymous type QName anonymousName = new QName(type.getName().getNamespaceURI() + "/" + type.getName().getLocalPart(), "AnonymousSuperType"); //$NON-NLS-1$ baseTypeDef = new AnonymousXmlType(anonymousName); XmlTypeUtil.configureSimpleType(type, simpleType, index, reporter); // set metadata XmlSchemaReader.setMetadata(type, simpleType, null); // no schema // location // available // at this // point } else { reporter.error(new IOMessageImpl( "Simple type restriction without base type, skipping type configuration.", null, restriction.getLineNumber(), restriction.getLinePosition())); return; } // set super type type.setSuperType(baseTypeDef); // mark as restriction type.setConstraint(RestrictionFlag.ENABLED); // assign no binding, inherit from super type // The following code expects schema to be valid. List<Validator> validators = new LinkedList<Validator>(); // patterns and enumerations in one step are ORed together! List<String> values = new LinkedList<String>(); List<Validator> patternValidators = new LinkedList<Validator>(); // TODO different handling for date/time/g.../duration in // (min|max)(In|Ex)clusive // XXX only for date, time, duration, dateTime, gMonthDay, gYearMonth? // no also for some cases of gYear, gMonth, gDay (they can have a // timezone!) // but only need to handle cases where isDecimal() is false... XmlSchemaObjectCollection facets = restriction.getFacets(); for (int i = 0; i < facets.getCount(); i++) { XmlSchemaFacet facet = (XmlSchemaFacet) facets.getItem(i); if (facet instanceof XmlSchemaEnumerationFacet) { values.add(facet.getValue().toString()); } else if (facet instanceof XmlSchemaFractionDigitsFacet) { validators.add(new DigitCountValidator(DigitCountValidator.Type.FRACTIONDIGITS, Integer.parseInt(facet.getValue().toString()))); } else if (facet instanceof XmlSchemaLengthFacet) { validators.add(new LengthValidator(LengthValidator.Type.EXACT, Integer .parseInt(facet.getValue().toString()))); } else if (facet instanceof XmlSchemaMaxExclusiveFacet) { if (isDecimal(facet.getValue().toString())) // number or date? validators.add(new NumberValidator(NumberValidator.Type.MAXEXCLUSIVE, new BigDecimal(facet.getValue().toString()))); else reporter.warn(new IOMessageImpl( "(min|max)(In|Ex)clusive not supported for non-number types", null, facet.getLineNumber(), facet.getLinePosition())); } else if (facet instanceof XmlSchemaMaxInclusiveFacet) { if (isDecimal(facet.getValue().toString())) // number or date? validators.add(new NumberValidator(NumberValidator.Type.MAXINCLUSIVE, new BigDecimal(facet.getValue().toString()))); else reporter.warn(new IOMessageImpl( "(min|max)(In|Ex)clusive not supported for non-number types", null, facet.getLineNumber(), facet.getLinePosition())); } else if (facet instanceof XmlSchemaMaxLengthFacet) { validators.add(new LengthValidator(LengthValidator.Type.MAXIMUM, Integer .parseInt(facet.getValue().toString()))); } else if (facet instanceof XmlSchemaMinLengthFacet) { validators.add(new LengthValidator(LengthValidator.Type.MINIMUM, Integer .parseInt(facet.getValue().toString()))); } else if (facet instanceof XmlSchemaMinExclusiveFacet) { if (isDecimal(facet.getValue().toString())) // number or date? validators.add(new NumberValidator(NumberValidator.Type.MINEXCLUSIVE, new BigDecimal(facet.getValue().toString()))); else reporter.warn(new IOMessageImpl( "(min|max)(In|Ex)clusive not supported for non-number types", null, facet.getLineNumber(), facet.getLinePosition())); } else if (facet instanceof XmlSchemaMinInclusiveFacet) { if (isDecimal(facet.getValue().toString())) // number or date? validators.add(new NumberValidator(NumberValidator.Type.MININCLUSIVE, new BigDecimal(facet.getValue().toString()))); else reporter.warn(new IOMessageImpl( "(min|max)(In|Ex)clusive not supported for non-number types", null, facet.getLineNumber(), facet.getLinePosition())); } else if (facet instanceof XmlSchemaPatternFacet) { patternValidators.add(new PatternValidator(facet.getValue().toString())); } else if (facet instanceof XmlSchemaTotalDigitsFacet) { validators.add(new DigitCountValidator(DigitCountValidator.Type.TOTALDIGITS, Integer.parseInt(facet.getValue().toString()))); } else if (facet instanceof XmlSchemaWhiteSpaceFacet) { reporter.info(new IOMessageImpl("White space facet not supported", null, facet .getLineNumber(), facet.getLinePosition())); // Nothing to validate according to w3. // Values should be processed according to rule? } else { reporter.error(new IOMessageImpl("Unrecognized facet: " + facet.getClass().getSimpleName(), null, facet.getLineNumber(), facet .getLinePosition())); } } if (!patternValidators.isEmpty()) validators.add(new OrValidator(patternValidators)); if (!values.isEmpty()) { // set enumeration constraint // no check of which values are okay, they must be validated // somewhere else. // XXX conversion to be done? type.setConstraint(new Enumeration<String>(values, false)); validators.add(new EnumerationValidator(values)); } if (!validators.isEmpty()) type.setConstraint(new ValidationConstraint(new AndValidator(validators), type)); } /** * Checks whether the given string can be converted to a decimal. * * @param s the string to check * @return true, iff the sting can be converted to a decimal */ private static boolean isDecimal(String s) { try { new BigDecimal(s); return true; } catch (NumberFormatException nfe) { return false; } } /** * Configure a type definition for a simple type based on a simple type * list. * * @param type the type definition * @param list the simple type list * @param index the XML index for resolving type definitions * @param reporter the report */ private static void configureSimpleTypeList(XmlTypeDefinition type, XmlSchemaSimpleTypeList list, XmlIndex index, IOReporter reporter) { XmlTypeDefinition elementType = null; if (list.getItemType() != null) { XmlSchemaSimpleType simpleType = list.getItemType(); if (simpleType.getQName() != null) { // named type elementType = index.getOrCreateType(simpleType.getQName()); } else { // anonymous type QName baseName = new QName(type.getName().getNamespaceURI() + "/" + type.getName().getLocalPart(), "AnonymousType"); //$NON-NLS-1$ //$NON-NLS-2$ elementType = new AnonymousXmlType(baseName); } configureSimpleType(elementType, simpleType, index, reporter); } else if (list.getItemTypeName() != null) { // named type elementType = index.getOrCreateType(list.getItemTypeName()); } if (elementType != null) { // set constraints on type // element type type.setConstraint(ElementType.createFromType(elementType)); // list binding type.setConstraint(Binding.get(List.class)); } else { reporter.error(new IOMessageImpl("Unrecognized base type for simple type list", null, list.getLineNumber(), list.getLinePosition())); } } /** * Configure a type definition for a simple type based on a simple type * union. * * @param type the type definition * @param union the simple type union * @param index the XML index for resolving type definitions * @param reporter the report */ private static void configureSimpleTypeUnion(XmlTypeDefinition type, XmlSchemaSimpleTypeUnion union, XmlIndex index, IOReporter reporter) { XmlSchemaObjectCollection baseTypes = union.getBaseTypes(); // collect type definitions Set<TypeDefinition> unionTypes = new HashSet<TypeDefinition>(); if (union.getMemberTypesQNames() != null) { for (QName unionMember : union.getMemberTypesQNames()) unionTypes.add(index.getOrCreateType(unionMember)); } // base type definitions if (baseTypes != null && baseTypes.getCount() > 0) { for (int i = 0; i < baseTypes.getCount(); i++) { XmlSchemaObject baseType = baseTypes.getItem(i); if (baseType instanceof XmlSchemaSimpleType) { XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType) baseType; // Here it is a xs:localSimpleTypes, name attribute is // prohibited! // So it always is a anonymous type. QName baseName = new QName(type.getName().getNamespaceURI() + "/" + type.getName().getLocalPart(), "AnonymousType" + i); //$NON-NLS-1$ //$NON-NLS-2$ XmlTypeDefinition baseDef = new AnonymousXmlType(baseName); configureSimpleType(baseDef, simpleType, index, reporter); unionTypes.add(baseDef); } else { reporter.error(new IOMessageImpl( "Unrecognized base type for simple type union", null, union .getLineNumber(), union.getLinePosition())); } } } // binding constraint type.setConstraint(new UnionBinding(unionTypes)); // enumeration constraint type.setConstraint(new UnionEnumeration(unionTypes)); // validation constraint type.setConstraint(new UnionValidationConstraint(unionTypes)); } /** * Determine if there is a special binding available for a type (apart from * explicit definition in the schema) * * @param type the type definition * @return the special binding or <code>null</code> */ public static boolean setSpecialBinding(XmlTypeDefinition type) { // determine special bindings // geometry bindings Geometries geoms = Geometries.getInstance(); try { Iterable<TypeConstraint> constraints = geoms.getTypeConstraints(type); if (constraints != null) { // set the geometry related constraints (Binding, ElementType, // GeometryType) for (TypeConstraint constraint : constraints) { type.setConstraint(constraint); } } // enable augmented value, as the derived geometry will be stored as // the value // XXX should this be done in handler?! type.setConstraint(AugmentedValueFlag.ENABLED); type.setConstraint(SkipGeometryValidation.getInstance()); } catch (GeometryNotSupportedException e) { // ignore - is no geometry or is not recognized } // XXX the old way // if (GML_GEOMETRY_TYPES.contains(type.getName())) { // //XXX just assign GeometryProperty binding for now // //FIXME concept of binding constraint and geometry property must be adapted to include built-in support for multiple geometries (with possible different CRS) // type.setConstraint(Binding.get(GeometryProperty.class)); // //TODO set geometry type? //// type.setConstraint(...); // // // enable augmented value, as the derived geometry will be stored as the value // type.setConstraint(AugmentedValueFlag.ENABLED); // return true; // } // otherwise the super type binding will be used return false; } }