/*
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.transform.stream.StreamSource;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaAnnotated;
import org.apache.ws.commons.schema.XmlSchemaAny;
import org.apache.ws.commons.schema.XmlSchemaAppInfo;
import org.apache.ws.commons.schema.XmlSchemaAttribute;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroup;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroupRef;
import org.apache.ws.commons.schema.XmlSchemaChoice;
import org.apache.ws.commons.schema.XmlSchemaCollection;
import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension;
import org.apache.ws.commons.schema.XmlSchemaComplexContentRestriction;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaContent;
import org.apache.ws.commons.schema.XmlSchemaContentModel;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaExternal;
import org.apache.ws.commons.schema.XmlSchemaForm;
import org.apache.ws.commons.schema.XmlSchemaGroup;
import org.apache.ws.commons.schema.XmlSchemaGroupRef;
import org.apache.ws.commons.schema.XmlSchemaImport;
import org.apache.ws.commons.schema.XmlSchemaInclude;
import org.apache.ws.commons.schema.XmlSchemaNotation;
import org.apache.ws.commons.schema.XmlSchemaObject;
import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
import org.apache.ws.commons.schema.XmlSchemaParticle;
import org.apache.ws.commons.schema.XmlSchemaSequence;
import org.apache.ws.commons.schema.XmlSchemaSimpleContentExtension;
import org.apache.ws.commons.schema.XmlSchemaSimpleContentRestriction;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.apache.ws.commons.schema.XmlSchemaType;
import org.apache.ws.commons.schema.constants.Constants;
import org.apache.ws.commons.schema.utils.NamespacePrefixList;
import com.google.common.collect.ImmutableSet;
import de.fhg.igd.slf4jplus.ALogger;
import de.fhg.igd.slf4jplus.ALoggerFactory;
import eu.esdihumboldt.hale.common.core.io.IOProvider;
import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException;
import eu.esdihumboldt.hale.common.core.io.ProgressIndicator;
import eu.esdihumboldt.hale.common.core.io.Value;
import eu.esdihumboldt.hale.common.core.io.ValueList;
import eu.esdihumboldt.hale.common.core.io.impl.AbstractIOProvider;
import eu.esdihumboldt.hale.common.core.io.impl.AbstractImportProvider;
import eu.esdihumboldt.hale.common.core.io.report.IOReport;
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.io.SchemaReader;
import eu.esdihumboldt.hale.common.schema.io.impl.AbstractSchemaReader;
import eu.esdihumboldt.hale.common.schema.model.ChildDefinition;
import eu.esdihumboldt.hale.common.schema.model.Definition;
import eu.esdihumboldt.hale.common.schema.model.DefinitionGroup;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.hale.common.schema.model.constraint.DisplayName;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.Cardinality;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.ChoiceFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.property.NillableFlag;
import eu.esdihumboldt.hale.common.schema.model.constraint.type.AbstractFlag;
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.impl.AbstractDefinition;
import eu.esdihumboldt.hale.common.schema.model.impl.DefaultGroupPropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.impl.DefaultPropertyDefinition;
import eu.esdihumboldt.hale.common.schema.model.impl.DefaultTypeDefinition;
import eu.esdihumboldt.hale.io.xsd.XMLSchemaIO;
import eu.esdihumboldt.hale.io.xsd.anytype.CustomTypeContentConfiguration;
import eu.esdihumboldt.hale.io.xsd.anytype.CustomTypeContentHelper;
import eu.esdihumboldt.hale.io.xsd.constraint.RestrictionFlag;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlAppInfo;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlAttributeFlag;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlElements;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlIdUnique;
import eu.esdihumboldt.hale.io.xsd.constraint.XmlMixedFlag;
import eu.esdihumboldt.hale.io.xsd.internal.Messages;
import eu.esdihumboldt.hale.io.xsd.model.HasNotInheritableValue;
import eu.esdihumboldt.hale.io.xsd.model.XmlAttribute;
import eu.esdihumboldt.hale.io.xsd.model.XmlAttributeGroup;
import eu.esdihumboldt.hale.io.xsd.model.XmlElement;
import eu.esdihumboldt.hale.io.xsd.model.XmlGroup;
import eu.esdihumboldt.hale.io.xsd.model.XmlIndex;
import eu.esdihumboldt.hale.io.xsd.reader.internal.AnonymousXmlType;
import eu.esdihumboldt.hale.io.xsd.reader.internal.HumboldtURIResolver;
import eu.esdihumboldt.hale.io.xsd.reader.internal.ProgressURIResolver;
import eu.esdihumboldt.hale.io.xsd.reader.internal.SubstitutionGroupProperty;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlAttributeGroupReferenceProperty;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlAttributeReferenceProperty;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlElementReferenceProperty;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlGroupReferenceProperty;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlTypeDefinition;
import eu.esdihumboldt.hale.io.xsd.reader.internal.XmlTypeUtil;
import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.ElementName;
import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.MappableUsingXsiType;
import eu.esdihumboldt.hale.io.xsd.reader.internal.constraint.XLinkReference;
import eu.esdihumboldt.util.Identifiers;
import gnu.trove.TObjectIntHashMap;
/**
* The main functionality of this class is to load an XML schema file (XSD) and
* create a schema with {@link TypeDefinition}s. This implementation is based on
* the Apache XmlSchema library (
* {@link "http://ws.apache.org/commons/XmlSchema/"}).
*
* It is necessary use this library instead of the GeoTools XML schema loader,
* because the GeoTools version cannot handle GML 3.2 based files.
*
* @author Simon Templer
* @author Bernd Schneiders
* @author Thorsten Reitz
*/
public class XmlSchemaReader extends AbstractSchemaReader {
/**
* Namespace prefix generator.
*/
private static final class NamespaceIdentifiers extends Identifiers<String> {
/**
* @see Identifiers#Identifiers(String, boolean, int)
*/
private NamespaceIdentifiers(String prefix, boolean useEquals, int startCounter) {
super(prefix, useEquals, startCounter);
}
/**
* Add existing identifiers (to avoid conflicts).
*
* @param objectsAndIdentifiers objects (namespaces) mapped to
* identifiers (prefixes)
*/
public void addIdentifiers(Map<String, String> objectsAndIdentifiers) {
for (Entry<String, String> entry : objectsAndIdentifiers.entrySet()) {
putObjectIdentifier(entry.getKey(), entry.getValue());
}
}
}
/**
* Name of the parameter specifying the elements that represent mapping
* relevant types.
*/
public static final String PARAM_RELEVANT_ELEMENTS = "relevantElements";
/**
* Name of the parameter specifying if only those types are deemed mappable,
* that have an associated global element definition.
*/
public static final String PARAM_ONLY_ELEMENTS_MAPPABLE = "onlyElementsMappable";
/**
* Name of the parameter specifying custom type content configuration.
*/
public static final String PARAM_CUSTOM_TYPE_CONTENT = "customTypeContent";
/**
* The display name constraint for choices
*/
private static final DisplayName DISPLAYNAME_CHOICE = new DisplayName("choice");
/**
* The log
*/
private static final ALogger _log = ALoggerFactory.getLogger(XmlSchemaReader.class);
/**
* Qualified name of the XLink reference attribute.
*/
private static final QName NAME_XLINK_REF = new QName("http://www.w3.org/1999/xlink", "href");
/**
* Name for virtual INSPIRE NilReason type with adapted enumeration.
*/
private static final QName INSPIRE_NILREASON_TYPENAME = new QName(
"http://www.esdi-humboldt.eu/hale/inspire/ext", "NilReasonType");
/**
* Values for the virtual INSPIRE NilReason type.
*/
private static final Collection<? extends String> INSPIRE_NILREASON_VALUES = ImmutableSet
.of("unknown", "other:unpopulated", "withheld");
/**
* The XML definition index
*/
private XmlIndex index;
/**
* Holds the number of created groups for a parent. The parent identifier is
* mapped to the number of groups.
*/
private TObjectIntHashMap<String> groupCounter;
/**
* The current reporter
*/
private IOReporter reporter;
/**
* The generated namespace prefixes
*/
private final NamespaceIdentifiers namespaceGeneratedPrefixes = new NamespaceIdentifiers("ns",
true, 1);
/**
* @see SchemaReader#getSchema()
*/
@Override
public XmlIndex getSchema() {
return index;
}
/**
* @see IOProvider#isCancelable()
*/
@Override
public boolean isCancelable() {
return false;
}
/**
* @see AbstractImportProvider#validate()
*/
@Override
public void validate() throws IOProviderConfigurationException {
super.validate();
if (getSharedTypes() != null) {
for (TypeDefinition type : getSharedTypes().getTypes()) {
if (type instanceof XmlTypeDefinition) {
fail("Loading multiple XML schemas not supported, please create a combined XML schema instead.");
}
}
}
}
/**
* @see AbstractIOProvider#execute(ProgressIndicator, IOReporter)
*/
@Override
protected IOReport execute(ProgressIndicator progress, IOReporter reporter)
throws IOProviderConfigurationException, IOException {
progress.begin(Messages.getString("ApacheSchemaProvider.21"), ProgressIndicator.UNKNOWN); //$NON-NLS-1$
this.reporter = reporter;
XmlSchema xmlSchema = null;
XmlSchemaCollection schemaCol = new XmlSchemaCollection();
// Check if the file is located on web
URI location = getSource().getLocation();
if (location.getHost() == null) {
schemaCol.setSchemaResolver(
new ProgressURIResolver(new HumboldtURIResolver(), progress));
schemaCol.setBaseUri(findBaseUri(location));
}
else if (location.getScheme().equals("bundleresource")) { //$NON-NLS-1$
schemaCol.setSchemaResolver(
new ProgressURIResolver(new HumboldtURIResolver(), progress));
schemaCol.setBaseUri(findBaseUri(location) + "/"); //$NON-NLS-1$
}
else {
schemaCol.setSchemaResolver(
new ProgressURIResolver(new HumboldtURIResolver(), progress));
schemaCol.setBaseUri(findBaseUri(location) + "/"); //$NON-NLS-1$
}
InputStream is = getSource().getInput();
StreamSource ss = new StreamSource(is);
ss.setSystemId(location.toString());
xmlSchema = schemaCol.read(ss, null);
is.close();
String namespace = xmlSchema.getTargetNamespace();
if (namespace == null) {
namespace = XMLConstants.NULL_NS_URI;
}
xmlSchema.setSourceURI(location.toString());
// create index
index = new XmlIndex(namespace, location);
// create group counter
groupCounter = new TObjectIntHashMap<String>();
Set<String> imports = new HashSet<String>();
imports.add(location.toString());
// load XML Schema schema (for base type definitions)
try {
is = XmlSchemaReader.class.getResourceAsStream("/schemas/XMLSchema.xsd");
ss = new StreamSource(is);
schemaCol.setSchemaResolver(
new ProgressURIResolver(new HumboldtURIResolver(), progress));
schemaCol.setBaseUri(
findBaseUri(XmlSchemaReader.class.getResource("/schemas/XMLSchema.xsd").toURI())
+ "/");
XmlSchema xsSchema = schemaCol.read(ss, null);
is.close();
xsSchema.setSourceURI("http://www.w3.org/2001/XMLSchema.xsd");
XmlSchemaImport xmlSchemaImport = new XmlSchemaImport();
xmlSchemaImport.setSchema(xsSchema);
// add it to includes as XmlSchemaImport (not XmlSchemaInclude!)
xmlSchema.getIncludes().add(xmlSchemaImport);
} catch (Exception e) {
_log.error("Exception while loading XML Schema schema", e);
}
loadSchema(location.toString(), xmlSchema, imports, progress, true);
groupCounter.clear();
// post processing
applyRelevantElements(index);
applyCustomTypeContent(index);
reporter.setSuccess(true);
return reporter;
}
/**
* Apply custom type content configuration.
*
* @param index the XML index
*/
private void applyCustomTypeContent(XmlIndex index) {
CustomTypeContentConfiguration config = getParameter(PARAM_CUSTOM_TYPE_CONTENT)
.as(CustomTypeContentConfiguration.class);
if (config != null) {
CustomTypeContentHelper.applyConfigurations(index, config);
}
}
/**
* Apply the relevant elements setting to the given XML index.
*
* @param index the XML index
*/
private void applyRelevantElements(XmlIndex index) {
Set<? extends QName> names = getRelevantElements();
if (names != null && !names.isEmpty()) {
// only apply if any elements are given
// get all currently marked relevant types
Set<TypeDefinition> toggleTypes = new HashSet<>(index.getMappingRelevantTypes());
boolean foundAny = false;
for (QName name : names) {
XmlElement elm = index.getElements().get(name);
if (elm != null) {
foundAny = true;
TypeDefinition type = elm.getType();
if (toggleTypes.contains(type)) {
// do not toggle -> stay relevant
toggleTypes.remove(type);
}
else {
// toggle -> become relevant
toggleTypes.add(type);
}
}
}
if (foundAny) {
// only apply if one of the given elements was actually found in
// the schema
index.toggleMappingRelevant(toggleTypes);
}
}
}
/**
* Set the element names of mapping relevant types.
*
* @param elementNames the element names
*/
public void setRelevantElements(Collection<? extends QName> elementNames) {
ValueList elementList = new ValueList();
for (QName name : elementNames) {
elementList.add(Value.of(name));
}
setParameter(PARAM_RELEVANT_ELEMENTS, elementList.toValue());
}
/**
* @return the names of the elements configured as relevant
*/
public Set<? extends QName> getRelevantElements() {
Set<QName> result = new HashSet<>();
ValueList elementList = getParameter(PARAM_RELEVANT_ELEMENTS).as(ValueList.class);
if (elementList != null) {
for (Value val : elementList) {
QName name = val.as(QName.class);
if (name != null) {
result.add(name);
}
}
}
return result;
}
/**
* Set if only elements should be mappable. Otherwise all types with a
* global type definition are mappable.
*
* @param onlyElements if only elements should be mappable
*/
public void setOnlyElementsMappable(boolean onlyElements) {
setParameter(PARAM_ONLY_ELEMENTS_MAPPABLE, Value.of(onlyElements));
}
/**
* @return states if only types with associated global elements are
* classified as mappable types
*/
public boolean isOnlyElementsMappable() {
return getParameter(PARAM_ONLY_ELEMENTS_MAPPABLE).as(Boolean.class, true);
}
/**
* Load the feature types defined by the given schema
*
* @param schemaLocation the schema location
* @param xmlSchema the schema
* @param imports the imports/includes that were already loaded or where
* loading has been started
* @param progress the progress indicator
* @param mainSchema states if this is a main schema and therefore elements
* declared here should be flagged mappable
*/
protected void loadSchema(String schemaLocation, XmlSchema xmlSchema, Set<String> imports,
ProgressIndicator progress, boolean mainSchema) {
String namespace = xmlSchema.getTargetNamespace();
if (namespace == null) {
namespace = XMLConstants.NULL_NS_URI;
}
// add namespace prefixes
NamespacePrefixList namespaces = xmlSchema.getNamespaceContext();
addPrefixes(namespaces, namespace, mainSchema);
// the schema items
XmlSchemaObjectCollection items = xmlSchema.getItems();
// go through all schema items
for (int i = 0; i < items.getCount(); i++) {
XmlSchemaObject item = items.getItem(i);
if (item instanceof XmlSchemaElement) {
// global element declaration
XmlSchemaElement element = (XmlSchemaElement) item;
// determine type
XmlTypeDefinition elementType = null;
if (element.getSchemaTypeName() != null) {
// reference to type
elementType = index.getOrCreateType(element.getSchemaTypeName());
}
else if (element.getSchemaType() != null) {
// element has internal type definition, generate anonymous
// type name
QName typeName = new QName(element.getQName().getNamespaceURI(),
element.getQName().getLocalPart() + "_AnonymousType"); //$NON-NLS-1$
// create type
elementType = createType(element.getSchemaType(), typeName, schemaLocation,
namespace, mainSchema);
}
else if (element.getQName() != null) {
// element with no type
elementType = index.getOrCreateType(XmlTypeUtil.NAME_ANY_TYPE);
}
// XXX what about element.getRefName()?
if (elementType != null) {
// the element name
// XXX use element QName instead?
QName elementName = new QName(namespace, element.getName());
// the substitution group
QName subGroup = element.getSubstitutionGroup();
// TODO do we also need an index for substitutions?
// create schema element
XmlElement schemaElement = new XmlElement(elementName, elementType, subGroup);
// set metadata
setMetadata(schemaElement, element, schemaLocation);
// extend XmlElements constraint
XmlElements xmlElements = elementType.getConstraint(XmlElements.class);
xmlElements.addElement(schemaElement);
// set custom display name
elementType.setConstraint(new ElementName(xmlElements));
// set Mappable constraint (e.g. Mappable)
// for types with an associated element it can be determined
// on the spot if it is mappable
if (mainSchema) {
elementType.setConstraint(MappingRelevantFlag.get(true));
}
else {
// do not override with false, e.g. when a schema is
// loaded multiple times (e.g. because of different
// import locations)
elementType.setConstraintIfNotSet(MappingRelevantFlag.get(false));
}
// XXX needed? may result in conflicts when defining
// mappable types manually XXX the element is also marked
// with the Mappable constraint, to help with cases where
// multiple elements are defined for one
// schemaElement.setConstraint(MappableFlag.get(mainSchema));
// store element in index
index.getElements().put(elementName, schemaElement);
}
else {
reporter.error(new IOMessageImpl(
MessageFormat.format("No type for element {0} found.",
element.getName()),
null, element.getLineNumber(), element.getLinePosition()));
}
}
else if (item instanceof XmlSchemaType) {
// complex or simple type
createType((XmlSchemaType) item, null, schemaLocation, namespace, mainSchema);
}
else if (item instanceof XmlSchemaAttribute) {
// schema attribute that might be referenced somewhere
XmlSchemaAttribute att = (XmlSchemaAttribute) item;
if (att.getQName() != null) {
XmlTypeDefinition type = getAttributeType(att, null, schemaLocation);
if (type == null) {
// XXX if this occurs we might need a attribute
// referencing attribute
reporter.error(new IOMessageImpl("Could not determine attribute type", null,
att.getLineNumber(), att.getLinePosition()));
}
else {
XmlAttribute attribute = new XmlAttribute(att.getQName(), type);
index.getAttributes().put(attribute.getName(), attribute);
}
}
else {
reporter.warn(new IOMessageImpl(
MessageFormat.format("Attribute could not be processed: {0}",
att.getName()),
null, att.getLineNumber(), att.getLinePosition()));
}
}
else if (item instanceof XmlSchemaAttributeGroup) {
// schema attribute group that might be referenced somewhere
XmlSchemaAttributeGroup attributeGroup = (XmlSchemaAttributeGroup) item;
if (attributeGroup.getName() != null) {
String groupIdent = attributeGroup.getName().getNamespaceURI() + "/"
+ attributeGroup.getName().getLocalPart();
XmlAttributeGroup attGroup = new XmlAttributeGroup(groupIdent, true);
createAttributes(attributeGroup, attGroup, "", schemaLocation, namespace);
index.getAttributeGroups().put(attributeGroup.getName(), attGroup);
}
else {
reporter.warn(new IOMessageImpl("Attribute group could not be processed", null,
attributeGroup.getLineNumber(), attributeGroup.getLinePosition()));
}
}
else if (item instanceof XmlSchemaGroup) {
// group that might be referenced somewhere
XmlSchemaGroup schemaGroup = (XmlSchemaGroup) item;
if (schemaGroup.getName() != null) {
String groupIdent = schemaGroup.getName().getNamespaceURI() + "/"
+ schemaGroup.getName().getLocalPart();
XmlGroup group = new XmlGroup(groupIdent, true);
createPropertiesFromParticle(group, schemaGroup.getParticle(), schemaLocation,
namespace, false);
index.getGroups().put(schemaGroup.getName(), group);
}
else {
reporter.warn(new IOMessageImpl("Group could not be processed", null,
schemaGroup.getLineNumber(), schemaGroup.getLinePosition()));
}
}
else if (item instanceof XmlSchemaImport || item instanceof XmlSchemaInclude) {
// ignore, is treated separately
}
else if (item instanceof XmlSchemaNotation) {
// notations are ignored
}
else {
reporter.error(new IOMessageImpl(
"Unrecognized global definition: " + item.getClass().getSimpleName(), null,
item.getLineNumber(), item.getLinePosition()));
}
}
// Set of include locations
Set<String> includes = new HashSet<String>();
// handle imports
XmlSchemaObjectCollection externalItems = xmlSchema.getIncludes();
if (externalItems.getCount() > 0) {
_log.info("Loading includes and imports for schema at " + schemaLocation); //$NON-NLS-1$
}
for (int i = 0; i < externalItems.getCount(); i++) {
try {
XmlSchemaExternal imp = (XmlSchemaExternal) externalItems.getItem(i);
XmlSchema importedSchema = imp.getSchema();
String location = importedSchema.getSourceURI();
if (!(imports.contains(location))) { // only add schemas that
// were not already
// added
imports.add(location); // place a marker in the map to
// prevent loading the location in
// the call to loadSchema
loadSchema(location, importedSchema, imports, progress,
mainSchema && imp instanceof XmlSchemaInclude);
// is part of main schema if it's a main schema include
}
if (imp instanceof XmlSchemaInclude) {
includes.add(location);
}
} catch (Throwable e) {
reporter.error(new IOMessageImpl(
"Error adding imported schema from " + schemaLocation, e)); // $NON-NLS-1$
}
}
_log.info("Creating types for schema at " + schemaLocation); //$NON-NLS-1$
progress.setCurrentTask(
MessageFormat.format(Messages.getString("ApacheSchemaProvider.33"), namespace)); //$NON-NLS-1$
}
/**
* Add namespace prefixes from a schema to the XmlIndex.
*
* @param namespaces the namespace prefixes defined in the (single) schema
* @param defaultNamespace the default namespace of the schema
* @param mainSchema specifies if the schema is the main schema
*/
private void addPrefixes(NamespacePrefixList namespaces, String defaultNamespace,
boolean mainSchema) {
Map<String, String> prefixes = index.getPrefixes(); // namespaces mapped
// to prefixes
Set<String> orphanedNamespaces = new HashSet<String>();
for (String prefix : namespaces.getDeclaredPrefixes()) {
String ns = namespaces.getNamespaceURI(prefix);
if (!prefixes.containsKey(ns)) {
// prefix for namespace is not yet included
if (prefixes.containsValue(prefix)) {
// prefix already there, may not override
orphanedNamespaces.add(ns);
}
else {
// ok to use prefix
prefixes.put(ns, prefix);
}
}
}
// update namespace identifiers with current prefixes
namespaceGeneratedPrefixes.addIdentifiers(prefixes);
// handle orphaned namespaces
for (String ns : orphanedNamespaces) {
if (!XMLConstants.XML_NS_URI.equals(ns)) {
// exclude XML namespace, its prefix is fixed
String prefix = namespaceGeneratedPrefixes.getId(ns);
prefixes.put(ns, prefix);
}
}
// special handling of default namespace (add it if not known)
if (!mainSchema && !prefixes.containsKey(defaultNamespace)
&& !XMLConstants.XML_NS_URI.equals(defaultNamespace)) {
// exclude XML namespace, its prefix is fixed
// generate a namespace prefix for imported schemas that might have
// none
String prefix = namespaceGeneratedPrefixes.getId(defaultNamespace);
prefixes.put(defaultNamespace, prefix);
}
}
/**
* Create a type definition from the given schema type and add it to the
* index or enhance an existing type definition if it is already in the
* index.
*
* @param schemaType the schema type
* @param typeName the type name to use for the type, <code>null</code> if
* the name of the schema type shall be used
* @param schemaLocation the schema location
* @param schemaNamespace the schema namespace
* @param mainSchema if the type definition is a global definition in a main
* schema
* @return the created type
*/
private XmlTypeDefinition createType(XmlSchemaType schemaType, QName typeName,
String schemaLocation, String schemaNamespace, boolean mainSchema) {
if (typeName == null) {
typeName = schemaType.getQName();
}
// get type definition from index
XmlTypeDefinition type = index.getOrCreateType(typeName);
if (schemaType instanceof XmlSchemaSimpleType) {
// attribute type from simple schema types
configureSimpleType(type, (XmlSchemaSimpleType) schemaType, schemaLocation);
}
else if (schemaType instanceof XmlSchemaComplexType) {
XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaType;
// determine the super type name
QName superTypeName = getSuperTypeName(complexType);
// determine if the super type relation is a restriction
boolean isRestriction = isRestriction(complexType);
type.setConstraint(
(isRestriction) ? (RestrictionFlag.ENABLED) : (RestrictionFlag.DISABLED));
if (superTypeName != null) {
// get super type from index
XmlTypeDefinition superType = index.getOrCreateType(superTypeName);
type.setSuperType(superType);
// XXX reuse the super type's attribute type where appropriate?
}
// set mappable constraint
// don't override mappable if explicitly set to false
if (isOnlyElementsMappable()) {
// only types with a global element definition (or with a super
// type that matches this condition)
type.setConstraintIfNotSet(new MappableUsingXsiType(type));
}
else {
// all global complex type definitions should be mappable
type.setConstraintIfNotSet(MappableFlag.ENABLED);
}
// set type metadata and constraints
setMetadataAndConstraints(type, complexType, schemaLocation);
// determine the defined properties and add them to the declaring
// type
createProperties(type, complexType, schemaLocation, schemaNamespace);
}
else {
reporter.error(new IOMessageImpl(
"Unrecognized schema type: " + schemaType.getClass().getSimpleName(), null,
schemaType.getLineNumber(), schemaType.getLinePosition()));
}
return type;
}
/**
* Configure a type definition for a simple type
*
* @param type the type definition
* @param schemaType the schema simple type
* @param schemaLocation the schema location
*/
private void configureSimpleType(XmlTypeDefinition type, XmlSchemaSimpleType schemaType,
String schemaLocation) {
XmlTypeUtil.configureSimpleType(type, schemaType, index, reporter);
// set metadata
setMetadata(type, schemaType, schemaLocation);
}
private static URI createLocationURI(String schemaLocation, XmlSchemaObject schemaObject) {
if (schemaLocation == null) {
return null;
}
// XXX improve
try {
return new URI(schemaLocation + "#" + schemaObject.getLineNumber() + ":"
+ schemaObject.getLinePosition());
} catch (URISyntaxException e) {
// ignore
return null;
}
}
/**
* @see AbstractIOProvider#getDefaultTypeName()
*/
@Override
protected String getDefaultTypeName() {
return "XML schema";
}
/**
* Extracts attribute definitions from a {@link XmlSchemaParticle}.
*
* @param declaringGroup the definition of the declaring group
* @param particle the particle
* @param schemaLocation the schema location
* @param schemaNamespace the schema namespace
* @param forceGroup force creating a group (e.g. if the parent is a choice)
*/
private void createPropertiesFromParticle(DefinitionGroup declaringGroup,
XmlSchemaParticle particle, String schemaLocation, String schemaNamespace,
boolean forceGroup) {
// particle:
if (particle instanceof XmlSchemaSequence) {
// <sequence>
XmlSchemaSequence sequence = (XmlSchemaSequence) particle;
// create group only if necessary (sequences that appear exactly
// once will result in no group if not forced)
if (forceGroup || sequence.getMinOccurs() != 1 || sequence.getMaxOccurs() != 1) {
// create a sequence group
QName sequenceName = createGroupName(declaringGroup, "sequence");
DefaultGroupPropertyDefinition sequenceGroup = new DefaultGroupPropertyDefinition(
sequenceName, declaringGroup, false);
// set cardinality
long max = (sequence.getMaxOccurs() == Long.MAX_VALUE) ? (Cardinality.UNBOUNDED)
: (sequence.getMaxOccurs());
sequenceGroup.setConstraint(Cardinality.get(sequence.getMinOccurs(), max));
// set choice constraint (no choice)
sequenceGroup.setConstraint(ChoiceFlag.DISABLED);
// set metadata
setMetadata(sequenceGroup, sequence, schemaLocation);
// use group as parent
declaringGroup = sequenceGroup;
}
for (int j = 0; j < sequence.getItems().getCount(); j++) {
XmlSchemaObject object = sequence.getItems().getItem(j);
if (object instanceof XmlSchemaElement) {
// <element>
createPropertyFromElement((XmlSchemaElement) object, declaringGroup,
schemaLocation, schemaNamespace);
// </element>
}
else if (object instanceof XmlSchemaParticle) {
// contained particles, e.g. a choice
// content doesn't need to be grouped, it can be decided in
// the method
createPropertiesFromParticle(declaringGroup, (XmlSchemaParticle) object,
schemaLocation, schemaNamespace, false);
}
}
// </sequence>
}
else if (particle instanceof XmlSchemaChoice) {
// <choice>
XmlSchemaChoice choice = (XmlSchemaChoice) particle;
// create a choice group
QName choiceName = createGroupName(declaringGroup, "choice");
DefaultGroupPropertyDefinition choiceGroup = new DefaultGroupPropertyDefinition(
choiceName, declaringGroup, false); // no flatten allowed
// because of choice
// set custom display name
choiceGroup.setConstraint(DISPLAYNAME_CHOICE);
// set cardinality
long max = (choice.getMaxOccurs() == Long.MAX_VALUE) ? (Cardinality.UNBOUNDED)
: (choice.getMaxOccurs());
choiceGroup.setConstraint(Cardinality.get(choice.getMinOccurs(), max));
// set choice constraint
choiceGroup.setConstraint(ChoiceFlag.ENABLED);
// set metadata
setMetadata(choiceGroup, choice, schemaLocation);
// create properties with choiceGroup as parent
for (int j = 0; j < choice.getItems().getCount(); j++) {
XmlSchemaObject object = choice.getItems().getItem(j);
if (object instanceof XmlSchemaElement) {
// <element>
createPropertyFromElement((XmlSchemaElement) object, choiceGroup,
schemaLocation, schemaNamespace);
}
else if (object instanceof XmlSchemaParticle) {
// contained particles, e.g. a choice or sequence
// inside a choice they must form a group
createPropertiesFromParticle(choiceGroup, (XmlSchemaParticle) object,
schemaLocation, schemaNamespace, true);
}
}
// </choice>
}
else if (particle instanceof XmlSchemaGroupRef) {
// <group ref="..." />
XmlSchemaGroupRef groupRef = (XmlSchemaGroupRef) particle;
QName groupName = groupRef.getRefName();
long max = (groupRef.getMaxOccurs() == Long.MAX_VALUE) ? (Cardinality.UNBOUNDED)
: (groupRef.getMaxOccurs());
long min = groupRef.getMinOccurs();
/*
* Only allow flatten if group is not forced and appears exactly
* once
*/
XmlGroupReferenceProperty property = new XmlGroupReferenceProperty(groupName,
declaringGroup, index, groupName, !forceGroup && min == 1 && max == 1);
// set cardinality constraint
property.setConstraint(Cardinality.get(min, max));
// set metadata
setMetadata(property, groupRef, schemaLocation);
}
else if (particle instanceof XmlSchemaAny) {
// XXX ignore for now
reporter.info(new IOMessageImpl("Particle that allows any element is not supported.",
null, particle.getLineNumber(), particle.getLinePosition()));
}
else {
reporter.error(new IOMessageImpl(
"Unrecognized particle: " + particle.getClass().getSimpleName(), null,
particle.getLineNumber(), particle.getLinePosition()));
}
}
/**
* Create a name for a group.
*
* @param declaringGroup the declaring group
* @param groupType the group type
* @return the group name
*/
private QName createGroupName(DefinitionGroup declaringGroup, String groupType) {
int groupNumber;
synchronized (groupCounter) {
groupNumber = groupCounter.get(declaringGroup.getIdentifier()) + 1;
groupCounter.put(declaringGroup.getIdentifier(), groupNumber);
}
return new QName(declaringGroup.getIdentifier(), groupType + "_" + groupNumber);
}
/**
* Create a property from an element
*
* @param element the schema element
* @param declaringGroup the definition of the declaring group
* @param schemaLocation the schema location
* @param schemaNamespace the schema namespace
*/
private void createPropertyFromElement(XmlSchemaElement element, DefinitionGroup declaringGroup,
String schemaLocation, String schemaNamespace) {
if (element.getSchemaTypeName() != null) {
// element referencing a type
// <element name="ELEMENT_NAME" type="SCHEMA_TYPE_NAME" />
QName elementName = element.getQName();
SubstitutionGroupProperty substitutionGroup = new SubstitutionGroupProperty(
new QName(elementName.getNamespaceURI() + "/" + elementName.getLocalPart(),
"choice"), // TODO
// improve
// naming?
declaringGroup);
DefaultPropertyDefinition property = new DefaultPropertyDefinition(elementName,
substitutionGroup, index.getOrCreateType(element.getSchemaTypeName()));
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
substitutionGroup.setProperty(property);
}
else if (element.getRefName() != null) {
// references another element
// <element ref="REF_NAME" />
QName elementName = element.getRefName();
SubstitutionGroupProperty substitutionGroup = new SubstitutionGroupProperty(
new QName(elementName.getNamespaceURI() + "/" + elementName.getLocalPart(),
"choice"), // TODO
// improve
// naming?
declaringGroup);
XmlElementReferenceProperty property = new XmlElementReferenceProperty(elementName,
substitutionGroup, index, elementName);
// set metadata and constraints FIXME can the constraints be set at
// this point? or must the property determine them from the
// SchemaElement?
setMetadataAndConstraints(property, element, schemaLocation);
substitutionGroup.setProperty(property);
}
else if (element.getSchemaType() != null) {
// element w/o type name or reference but an internal type
// definition
if (element.getSchemaType() instanceof XmlSchemaComplexType) {
// <element ...>
// <complexType>
XmlSchemaComplexType complexType = (XmlSchemaComplexType) element.getSchemaType();
XmlSchemaContentModel model = complexType.getContentModel();
if (model != null) {
XmlSchemaContent content = model.getContent();
QName superTypeName = null;
if (content instanceof XmlSchemaComplexContentExtension
|| content instanceof XmlSchemaComplexContentRestriction) {
// <complexContent>
// <extension base="..."> / <restriction ...>
String nameExt;
if (content instanceof XmlSchemaComplexContentExtension) {
superTypeName = ((XmlSchemaComplexContentExtension) content)
.getBaseTypeName();
nameExt = "Extension"; //$NON-NLS-1$
}
else {
superTypeName = ((XmlSchemaComplexContentRestriction) content)
.getBaseTypeName();
nameExt = "Restriction"; //$NON-NLS-1$
}
if (superTypeName != null) {
// try to get the type definition of the super type
XmlTypeDefinition superType = index.getOrCreateType(superTypeName);
// create an anonymous type that extends the super
// type
QName anonymousName = new QName(
getTypeIdentifier(declaringGroup) + "/" + element.getName(),
superTypeName.getLocalPart() + nameExt); // $NON-NLS-1$
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
anonymousType.setSuperType(superType);
// set metadata and constraints
setMetadataAndConstraints(anonymousType, complexType, schemaLocation);
// add properties to the anonymous type
createProperties(anonymousType, complexType, schemaLocation,
schemaNamespace);
// create a property with the anonymous type
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
element.getQName(), declaringGroup, anonymousType);
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
}
else {
reporter.error(new IOMessageImpl(
"Could not determine super type for complex content", null,
content.getLineNumber(), content.getLinePosition()));
}
// </extension> / </restriction>
// </complexContent>
}
else if (content instanceof XmlSchemaSimpleContentExtension
|| content instanceof XmlSchemaSimpleContentRestriction) {
// <simpleContent>
// <extension base="..."> / <restriction ...>
String nameExt;
if (content instanceof XmlSchemaSimpleContentExtension) {
superTypeName = ((XmlSchemaSimpleContentExtension) content)
.getBaseTypeName();
nameExt = "Extension"; //$NON-NLS-1$
}
else {
superTypeName = ((XmlSchemaSimpleContentRestriction) content)
.getBaseTypeName();
nameExt = "Restriction"; //$NON-NLS-1$
}
if (superTypeName != null) {
// try to get the type definition of the super type
XmlTypeDefinition superType = index.getOrCreateType(superTypeName);
// create an anonymous type that extends the super
// type
QName anonymousName = new QName(
getTypeIdentifier(declaringGroup) + "/" + element.getName(),
superTypeName.getLocalPart() + nameExt); // $NON-NLS-1$
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
anonymousType.setSuperType(superType);
// set metadata and constraints
setMetadata(anonymousType, complexType, schemaLocation);
anonymousType.setConstraint(HasValueFlag.ENABLED);
// set no binding, inherit it from the super type
// XXX is this ok?
// add properties to the anonymous type
createProperties(anonymousType, complexType, schemaLocation,
schemaNamespace);
// create a property with the anonymous type
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
element.getQName(), declaringGroup, anonymousType);
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
}
else {
reporter.error(new IOMessageImpl(
"Could not determine super type for simple content", null,
content.getLineNumber(), content.getLinePosition()));
}
// </extension>
// </simpleContent>
}
}
else {
// this where we get when there is an anonymous complex type
// as property type
// create an anonymous type
QName anonymousName = new QName(
getTypeIdentifier(declaringGroup) + "/" + element.getName(),
"AnonymousType");
// create anonymous type with no super type
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
// set metadata and constraints
setMetadataAndConstraints(anonymousType, complexType, schemaLocation);
// add properties to the anonymous type
createProperties(anonymousType, complexType, schemaLocation, schemaNamespace);
// create a property with the anonymous type
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
element.getQName(), declaringGroup, anonymousType);
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
}
// </complexType>
// </element>
}
else if (element.getSchemaType() instanceof XmlSchemaSimpleType) {
// simple schema type
XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType) element.getSchemaType();
// create an anonymous type
QName anonymousName = new QName(
getTypeIdentifier(declaringGroup) + "/" + element.getName(),
"AnonymousType"); //$NON-NLS-1$
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
configureSimpleType(anonymousType, simpleType, schemaLocation);
// create a property with the anonymous type
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
element.getQName(), declaringGroup, anonymousType);
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
}
}
else {
// <element name="..." />
// no type defined
reporter.warn(new IOMessageImpl("Element definition without an associated type: {0}",
null, element.getLineNumber(), element.getLinePosition(), element.getQName()));
// assuming xsd:anyType as default type
QName elementName = element.getQName();
SubstitutionGroupProperty substitutionGroup = new SubstitutionGroupProperty(
new QName(elementName.getNamespaceURI() + "/" + elementName.getLocalPart(),
"choice"), // TODO
// improve
// naming?
declaringGroup);
DefaultPropertyDefinition property = new DefaultPropertyDefinition(elementName,
substitutionGroup, index.getOrCreateType(XmlTypeUtil.NAME_ANY_TYPE));
// set metadata and constraints
setMetadataAndConstraints(property, element, schemaLocation);
substitutionGroup.setProperty(property);
}
}
/**
* Get a type identifier from the given group. If the group is a type
* definition its identifier will be returned, if it is a child definition
* the identifier of the parent type will be returned.
*
* @param group the group
* @return the type identifier
* @throws IllegalArgumentException if the group is neither a type nor a
* child definition
*/
private static String getTypeIdentifier(DefinitionGroup group) throws IllegalArgumentException {
if (group instanceof TypeDefinition) {
return ((TypeDefinition) group).getIdentifier();
}
else if (group instanceof ChildDefinition<?>) {
return ((ChildDefinition<?>) group).getParentType().getIdentifier();
}
return null;
}
/**
* Set metadata and constraints for a complex type
*
* @param type the type definition
* @param complexType the complex type definition
* @param schemaLocation the schema location
*/
private void setMetadataAndConstraints(XmlTypeDefinition type, XmlSchemaComplexType complexType,
String schemaLocation) {
type.setConstraint(AbstractFlag.get(complexType.isAbstract()));
/*
* HasValue and Binding and all other inheritable constraints from super
* type, override constraints for special types
*/
// special bindings (geometries)
XmlTypeUtil.setSpecialBinding(type);
// mixed types
if (complexType.isMixed()) {
if (type.getName().equals(XmlTypeUtil.NAME_ANY_TYPE)) {
// prevent enabling HasValueFlag on anyType
// type.setConstraint(HasValueFlag.DISABLED);
// anyType may have a simple value like a string etc.
type.setConstraint(HasNotInheritableValue.INSTANCE);
}
else {
// XXX how to treat mixed type?
// XXX for now represent as a string value
/*
* Types inheriting from a mixed don't necessarily are mixed
* themselves.
*/
type.setConstraint(HasNotInheritableValue.INSTANCE);
}
/*
* XXX String binding is a problem as it is inherited to non-mixed
* types
*/
// type.setConstraint(Binding.get(String.class));
// mark as mixed type
type.setConstraint(XmlMixedFlag.ENABLED);
}
// set metadata
setMetadata(type, complexType, schemaLocation);
}
/**
* Set the metadata for a definition
*
* @param definition the definition
* @param annotated the XML annotated object
* @param schemaLocation the schema location
*/
public static void setMetadata(AbstractDefinition<?> definition, XmlSchemaAnnotated annotated,
String schemaLocation) {
definition.setDescription(XMLSchemaIO.getDescription(annotated));
List<XmlSchemaAppInfo> appInfo = XMLSchemaIO.getAppInfo(annotated);
if (appInfo != null) {
XmlAppInfo constraint = new XmlAppInfo(appInfo);
if (definition instanceof DefaultPropertyDefinition) {
((DefaultPropertyDefinition) definition).setConstraint(constraint);
}
else if (definition instanceof DefaultGroupPropertyDefinition) {
((DefaultGroupPropertyDefinition) definition).setConstraint(constraint);
}
else if (definition instanceof DefaultTypeDefinition) {
((DefaultTypeDefinition) definition).setConstraint(constraint);
}
}
definition.setLocation(createLocationURI(schemaLocation, annotated));
}
/**
* Set metadata and constraints for a property based on a XML element
*
* @param property the property
* @param element the XML element
* @param schemaLocation the schema location
*/
private void setMetadataAndConstraints(DefaultPropertyDefinition property,
XmlSchemaElement element, String schemaLocation) {
property.setConstraint(new XmlIdUnique(property));
// set constraints
property.setConstraint(NillableFlag.get(element.isNillable()));
long max = (element.getMaxOccurs() == Long.MAX_VALUE) ? (Cardinality.UNBOUNDED)
: (element.getMaxOccurs());
property.setConstraint(Cardinality.get(element.getMinOccurs(), max));
// set metadata
setMetadata(property, element, schemaLocation);
}
/**
* Find a super type name based on a complex type
*
* @param item the complex type defining a super type
* @return the name of the super type or <code>null</code>
*/
private QName getSuperTypeName(XmlSchemaComplexType item) {
QName qname = null;
XmlSchemaContentModel model = item.getContentModel();
if (model != null) {
XmlSchemaContent content = model.getContent();
if (content instanceof XmlSchemaComplexContentExtension) {
qname = ((XmlSchemaComplexContentExtension) content).getBaseTypeName();
}
else if (content instanceof XmlSchemaComplexContentRestriction) { // restriction
qname = ((XmlSchemaComplexContentRestriction) content).getBaseTypeName();
}
else if (content instanceof XmlSchemaSimpleContentExtension) {
qname = ((XmlSchemaSimpleContentExtension) content).getBaseTypeName();
}
else if (content instanceof XmlSchemaSimpleContentRestriction) { // restriction
qname = ((XmlSchemaSimpleContentRestriction) content).getBaseTypeName();
}
}
return qname;
}
/**
* Determines if the super type relation of the given item is a restriction
*
* @param item the complex type defining a super type
*
* @return if the super type relation of the given item is a restriction
*/
private boolean isRestriction(XmlSchemaComplexType item) {
XmlSchemaContentModel model = item.getContentModel();
if (model != null) {
XmlSchemaContent content = model.getContent();
if (content instanceof XmlSchemaComplexContentRestriction
|| content instanceof XmlSchemaSimpleContentRestriction) {
return true;
}
}
return false;
}
/**
* Create the properties for the given complex type
*
* @param typeDef the definition of the declaring type
* @param item the complex type item
* @param schemaLocation the schema location
* @param schemaNamespace the scheme namspace
*/
private void createProperties(XmlTypeDefinition typeDef, XmlSchemaComplexType item,
String schemaLocation, String schemaNamespace) {
// item:
// <complexType ...>
XmlSchemaContentModel model = item.getContentModel();
if (model != null) {
XmlSchemaContent content = model.getContent();
if (content instanceof XmlSchemaComplexContentExtension) {
// <complexContent>
// <extension base="...">
XmlSchemaComplexContentExtension extension = (XmlSchemaComplexContentExtension) content;
// particle (e.g. sequence)
if (extension.getParticle() != null) {
XmlSchemaParticle particle = extension.getParticle();
createPropertiesFromParticle(typeDef, particle, schemaLocation, schemaNamespace,
false);
}
// attributes
XmlSchemaObjectCollection attributeCollection = extension.getAttributes();
if (attributeCollection != null) {
createAttributesFromCollection(attributeCollection, typeDef, null,
schemaLocation, schemaNamespace);
}
// complex content does not have a value
// (only if it is mixed, which can override this setting)
typeDef.setConstraintIfNotSet(HasValueFlag.DISABLED);
// </extension>
// </complexContent>
}
else if (content instanceof XmlSchemaComplexContentRestriction) {
// <complexContent>
// <restriction base="...">
XmlSchemaComplexContentRestriction restriction = (XmlSchemaComplexContentRestriction) content;
// particle (e.g. sequence)
if (restriction.getParticle() != null) {
XmlSchemaParticle particle = restriction.getParticle();
createPropertiesFromParticle(typeDef, particle, schemaLocation, schemaNamespace,
false);
}
// attributes
XmlSchemaObjectCollection attributeCollection = restriction.getAttributes();
if (attributeCollection != null) {
createAttributesFromCollection(attributeCollection, typeDef, null,
schemaLocation, schemaNamespace);
}
// complex content does not have a value
// (only if it is mixed, which can override this setting)
typeDef.setConstraintIfNotSet(HasValueFlag.DISABLED);
// </restriction>
// </complexContent>
}
else if (content instanceof XmlSchemaSimpleContentExtension) {
// <simpleContent>
// <extension base="...">
XmlSchemaSimpleContentExtension extension = (XmlSchemaSimpleContentExtension) content;
// attributes
XmlSchemaObjectCollection attributeCollection = extension.getAttributes();
if (attributeCollection != null) {
createAttributesFromCollection(attributeCollection, typeDef, null,
schemaLocation, schemaNamespace);
}
// </extension>
// </simpleContent>
}
else if (content instanceof XmlSchemaSimpleContentRestriction) {
// <simpleContent>
// <restriction base="...">
XmlSchemaSimpleContentRestriction restriction = (XmlSchemaSimpleContentRestriction) content;
// attributes
XmlSchemaObjectCollection attributeCollection = restriction.getAttributes();
if (attributeCollection != null) {
createAttributesFromCollection(attributeCollection, typeDef, null,
schemaLocation, schemaNamespace);
}
// </restriction>
// </simpleContent>
}
}
else {
// no complex content (instead e.g. <sequence>)
XmlSchemaComplexType complexType = item;
// particle (e.g. sequence)
if (item.getParticle() != null) {
XmlSchemaParticle particle = complexType.getParticle();
createPropertiesFromParticle(typeDef, particle, schemaLocation, schemaNamespace,
false);
}
// attributes
XmlSchemaObjectCollection attributeCollection = complexType.getAttributes();
if (attributeCollection != null) {
createAttributesFromCollection(attributeCollection, typeDef, null, schemaLocation,
schemaNamespace);
}
}
// </complexType>
}
private void createAttributesFromCollection(XmlSchemaObjectCollection attributeCollection,
DefinitionGroup declaringType, String indexPrefix, String schemaLocation,
String schemaNamespace) {
if (indexPrefix == null) {
indexPrefix = ""; //$NON-NLS-1$
}
for (int index = 0; index < attributeCollection.getCount(); index++) {
XmlSchemaObject object = attributeCollection.getItem(index);
if (object instanceof XmlSchemaAttribute) {
// <attribute ... />
XmlSchemaAttribute attribute = (XmlSchemaAttribute) object;
createAttribute(attribute, declaringType, schemaLocation, schemaNamespace);
}
else if (object instanceof XmlSchemaAttributeGroup) {
XmlSchemaAttributeGroup group = (XmlSchemaAttributeGroup) object;
createAttributes(group, declaringType, indexPrefix + index, schemaLocation,
schemaNamespace);
}
else if (object instanceof XmlSchemaAttributeGroupRef) {
XmlSchemaAttributeGroupRef groupRef = (XmlSchemaAttributeGroupRef) object;
if (groupRef.getRefName() != null) {
QName groupName = groupRef.getRefName();
// XXX extend group name with namespace?
XmlAttributeGroupReferenceProperty property = new XmlAttributeGroupReferenceProperty(
groupName, declaringType, this.index, groupName, true);
// TODO add constraints?
// set metadata
setMetadata(property, groupRef, schemaLocation);
}
else {
reporter.error(new IOMessageImpl("Unrecognized attribute group reference", null,
object.getLineNumber(), object.getLinePosition()));
}
}
}
}
private void createAttributes(XmlSchemaAttributeGroup group, DefinitionGroup declaringType,
String index, String schemaLocation, String schemaNamespace) {
createAttributesFromCollection(group.getAttributes(), declaringType, index + "_", //$NON-NLS-1$
schemaLocation, schemaNamespace);
}
private void createAttribute(XmlSchemaAttribute attribute, DefinitionGroup declaringGroup,
String schemaLocation, String schemaNamespace) {
// create attributes
QName typeName = attribute.getSchemaTypeName();
if (typeName != null) {
QName attributeName = determineAttributeName(attribute, schemaNamespace);
// resolve type by name
XmlTypeDefinition type = getTypeForAttribute(typeName, declaringGroup, attributeName);
// create property
DefaultPropertyDefinition property = new DefaultPropertyDefinition(attributeName,
declaringGroup, type);
// set metadata and constraints
setMetadataAndConstraints(property, attribute, schemaLocation);
}
else if (attribute.getSchemaType() != null) {
XmlSchemaSimpleType simpleType = attribute.getSchemaType();
// create an anonymous type
QName anonymousName = new QName(
getTypeIdentifier(declaringGroup) + "/" + attribute.getName(), "AnonymousType"); //$NON-NLS-2$
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
configureSimpleType(anonymousType, simpleType, schemaLocation);
// create property
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
determineAttributeName(attribute, schemaNamespace), declaringGroup,
anonymousType);
// set metadata and constraints
setMetadataAndConstraints(property, attribute, schemaLocation);
}
else if (attribute.getRefName() != null) {
// <attribute ref="REF_NAME" />
// reference to a named attribute
QName attName = attribute.getRefName();
XmlAttributeReferenceProperty property = new XmlAttributeReferenceProperty(attName,
declaringGroup, this.index, attName);
// set metadata and constraints
setMetadataAndConstraints(property, attribute, schemaLocation);
}
else {
/*
* Attribute with no type given has anySimpleType, see
* "http://www.w3.org/TR/2001/REC-xmlschema-1-20010502/#cAttribute_Declarations"
*/
// resolve type by name
XmlTypeDefinition type = index.getOrCreateType(
new QName(XMLConstants.W3C_XML_SCHEMA_NS_URI, "anySimpleType"));
// create property
DefaultPropertyDefinition property = new DefaultPropertyDefinition(
determineAttributeName(attribute, schemaNamespace), declaringGroup, type);
// set metadata and constraints
setMetadataAndConstraints(property, attribute, schemaLocation);
}
}
/**
* Get the named type to use for a specific XML attribute.
*
* @param typeName the name of the referenced type
* @param declaringGroup the declaring group of the attribute
* @param attributeName the attribute name
* @return the type definition that should be used as the attribute type
*/
private XmlTypeDefinition getTypeForAttribute(QName typeName, DefinitionGroup declaringGroup,
QName attributeName) {
// special case handling
// XXX INSPIRE nilReason hack
// detect nilReason attribute TODO check GML namespace?
if (attributeName.getLocalPart().equals("nilReason")
&& "NilReasonType".equals(typeName.getLocalPart())
&& declaringGroup instanceof Definition<?>) {
Definition<?> parentDef = (Definition<?>) declaringGroup;
// determine if parent is defined in INSPIRE
if (parentDef.getName().getNamespaceURI() != null && parentDef.getName()
.getNamespaceURI().startsWith("http://inspire.ec.europa.eu/schemas")) {
// get or create custom INSPIRE NilReason type
XmlTypeDefinition customType = (XmlTypeDefinition) this.index
.getType(INSPIRE_NILREASON_TYPENAME);
if (customType == null) {
// not yet created, configure now
customType = this.index.getOrCreateType(INSPIRE_NILREASON_TYPENAME);
// use the original type as super type
customType.setSuperType(this.index.getOrCreateType(typeName));
// description with documentation of the values
customType.setDescription(
"Virtual type representing the GML NilReasonType adapted for the valid values specified by INSPIRE:\n\n"
+ "unknown:\nThe correct value for the specific spatial object is not known to, and not computable by, the data provider. However, a correct value may exist.\n"
+ "NOTE 'unknown' is applied on an object-by-object basis in a spatial data set.\n\n"
+ "unpopulated:\nThe characteristic is not part of the dataset maintained by the data provider. However, the characteristic may exist in the real world.\n"
+ "NOTE The characteristic receives this value for all objects in the spatial data set.\n\n"
+ "withheld:\nThe characteristic may exist, but is confidential and not divulged by the data provider.");
// define a custom enumeration based on valid INSPIRE void
// reasons
customType
.setConstraint(new Enumeration<String>(INSPIRE_NILREASON_VALUES, true));
}
return customType;
}
}
// default case
return this.index.getOrCreateType(typeName);
}
/**
* Determine the qualified attribute name for a XML Schema attribute.
*
* @param attribute the XML Schema attribute
* @param schemaNamespace the schema namespace
* @return the qualified name of the attribute
*/
private QName determineAttributeName(XmlSchemaAttribute attribute, String schemaNamespace) {
if (attribute.getForm().getValue().equals(XmlSchemaForm.QUALIFIED)
&& (attribute.getQName().getNamespaceURI() == null || attribute.getQName()
.getNamespaceURI().equals(XMLConstants.NULL_NS_URI))) {
/*
* It seems in this case the namespace is not included in
* attribute.getQName(). Is this a bug in the schema parser? As a
* workaround we provide the namespace.
*/
return new QName(schemaNamespace, attribute.getQName().getLocalPart());
}
return attribute.getQName();
}
/**
* Create the type definition for an attribute, if possible
*
* @param attribute the XML attribute
* @param index the name index string
* @param schemaLocation the schema location
* @return the type definition or <code>null</code>
*/
private XmlTypeDefinition getAttributeType(XmlSchemaAttribute attribute, String index,
String schemaLocation) {
// create attributes
QName typeName = attribute.getSchemaTypeName();
if (typeName != null) {
// resolve type by name
return this.index.getOrCreateType(typeName);
}
else if (attribute.getSchemaType() != null) {
XmlSchemaSimpleType simpleType = attribute.getSchemaType();
// create an anonymous type
QName anonymousName = new QName(
attribute.getQName().getNamespaceURI() + "/" + attribute.getName(), //$NON-NLS-1$
"AnonymousType");
AnonymousXmlType anonymousType = new AnonymousXmlType(anonymousName);
configureSimpleType(anonymousType, simpleType, schemaLocation);
return anonymousType;
}
else if (attribute.getRefName() != null) {
// <attribute ref="REF_NAME" />
// reference to a named attribute
// can't create type
return null;
}
return null;
}
/**
* Set metadata and constraints for a property based on a XML attribute
*
* @param property the property
* @param attribute the XML attribute
* @param schemaLocation the schema location
*/
private void setMetadataAndConstraints(DefaultPropertyDefinition property,
XmlSchemaAttribute attribute, String schemaLocation) {
property.setConstraint(new XmlIdUnique(property));
// set constraints
property.setConstraint(XmlAttributeFlag.ENABLED);
if (attribute.getUse() != null) {
long maxOccurs = (attribute.getUse().getValue()
.equals(Constants.BlockConstants.PROHIBITED)) ? (0) : (1);
long minOccurs = (attribute.getUse().getValue()
.equals(Constants.BlockConstants.REQUIRED)) ? (1) : (0);
property.setConstraint(Cardinality.get(minOccurs, maxOccurs));
}
property.setConstraint(NillableFlag.DISABLED);
// special handling for XLink references (which are always XML
// attributes)
if (NAME_XLINK_REF.equals(property.getName())) {
property.setConstraint(new XLinkReference());
}
// set metadata
setMetadata(property, attribute, schemaLocation);
}
/**
* Get the base URI for the given URI
*
* @param uri the URI
*
* @return the base URI as string
*/
private String findBaseUri(URI uri) {
String baseUri = ""; //$NON-NLS-1$
baseUri = uri.toString();
if (baseUri.matches("^.*?\\/.+")) { //$NON-NLS-1$
baseUri = baseUri.substring(0, baseUri.lastIndexOf("/")); //$NON-NLS-1$
}
_log.info("Base URI for schemas to be used: " + baseUri); //$NON-NLS-1$
return baseUri;
}
}