/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2009-2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.complex.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDAttributeUse;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDTypeDefinition;
import org.geotools.feature.NameImpl;
import org.geotools.feature.type.AbstractLazyComplexTypeImpl;
import org.geotools.feature.type.GeometryTypeImpl;
import org.geotools.feature.type.Types;
import org.geotools.geometry.jts.CurvedGeometry;
import org.geotools.util.logging.Logging;
import org.geotools.xml.Configuration;
import org.geotools.xml.SchemaIndex;
import org.geotools.xml.Schemas;
import org.geotools.xml.complex.FeatureTypeRegistryConfiguration;
import org.geotools.xs.XSSchema;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.ComplexType;
import org.opengis.feature.type.FeatureTypeFactory;
import org.opengis.feature.type.GeometryType;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.feature.type.Schema;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.InternationalString;
import org.xml.sax.helpers.NamespaceSupport;
import com.vividsolutions.jts.geom.Geometry;
/**
* A registry of GeoTools {@link AttributeType} and {@link AttributeDescriptor} lazily parsed from the EMF {@link XSDTypeDefinition} and
* {@link XSDElementDeclaration} added through {@link #addSchemas(SchemaIndex)}.
* <p>
* This class is meant to be used in conjunction with {@link EmfComplexFeatureReader}.
* </p>
*
* @author Gabriel Roldan
* @author Niels Charlier
*
*
*
*/
public class FeatureTypeRegistry {
private static final Logger LOGGER = Logging.getLogger("org.geotools.data.complex");
private List<SchemaIndex> schemas;
private HashMap<Name, AttributeDescriptor> descriptorRegistry;
private HashMap<Name, AttributeType> typeRegistry;
private HashMap<Name, AttributeType> anonTypeRegistry;
private FeatureTypeFactory typeFactory;
private FeatureTypeRegistryConfiguration helper;
private boolean includeAttributes;
private static AttributeType XMLATTRIBUTE_TYPE;
/**
* stack of currently being built type names, used by {@link #createType(Name, XSDTypeDefinition)} to prevent recursive type definitions by
* proxy'ing a type that appears to be already being constructed and thus still not in the type registry.
*/
private Stack<Name> processingTypes;
public FeatureTypeRegistry(FeatureTypeFactory typeFactory, FeatureTypeRegistryConfiguration helper) {
this(null, typeFactory,helper);
}
public FeatureTypeRegistry(NamespaceSupport namespaces, FeatureTypeFactory typeFactory, FeatureTypeRegistryConfiguration helper) {
this(namespaces, typeFactory, helper, false);
}
public FeatureTypeRegistry(NamespaceSupport namespaces, FeatureTypeFactory typeFactory, FeatureTypeRegistryConfiguration helper, boolean includeAttributes) {
schemas = new ArrayList<SchemaIndex>();
this.typeFactory = typeFactory;
descriptorRegistry = new HashMap<Name, AttributeDescriptor>();
typeRegistry = new HashMap<Name, AttributeType>();
anonTypeRegistry = new HashMap<Name, AttributeType>();
processingTypes = new Stack<Name>();
this.helper = helper;
this.includeAttributes = includeAttributes;
createFoundationTypes();
}
public void addSchemas(final SchemaIndex schemaIndex) {
schemas.add(schemaIndex);
}
/**
* Destroy all schema Indexes. VERY important to that this is called to avoid memory leaks, because schema indexes are kept alive otherwise by
* static schema's and in this way keep other schema's alive
*/
public void disposeSchemaIndexes() {
for (SchemaIndex schemaIndex : schemas) {
schemaIndex.destroy();
}
}
public AttributeDescriptor getDescriptor(final Name descriptorName,
CoordinateReferenceSystem crs) {
AttributeDescriptor descriptor = descriptorRegistry.get(descriptorName);
if (descriptor == null) {
// top level elements
XSDElementDeclaration elemDecl = getElementDeclaration(descriptorName);
descriptor = createAttributeDescriptor(null, elemDecl, crs);
LOGGER.finest("Registering attribute descriptor " + descriptor.getName());
register(descriptor);
}
return descriptor;
}
private XSDElementDeclaration getElementDeclaration(final Name descriptorName) {
QName qname = Types.toQName(descriptorName);
XSDElementDeclaration elemDecl = null;
for (SchemaIndex schemaIndex : schemas) {
elemDecl = schemaIndex.getElementDeclaration(qname);
if (elemDecl != null) {
break;
}
}
if (elemDecl == null) {
String msg = "No top level element found in schemas: " + qname;
LOGGER.log(Level.WARNING, msg);
throw new NoSuchElementException(msg);
}
return elemDecl;
}
public AttributeType getAttributeType(final Name typeName) {
return getAttributeType(typeName, null, null);
}
public AttributeType getAttributeType(final Name typeName, XSDTypeDefinition xsdType,
CoordinateReferenceSystem crs) {
AttributeType type = typeRegistry.get(typeName);
if (type == null || type instanceof AbstractLazyComplexTypeImpl) {
// recreate lazy types too
if (xsdType == null) {
xsdType = getTypeDefinition(typeName);
}
LOGGER.finest("Creating attribute type " + typeName);
type = createType(typeName, xsdType, crs, false);
LOGGER.finest("Registering attribute type " + typeName);
}
return type;
}
private void setSubstitutionGroup(XSDComplexTypeDefinition container,
XSDElementDeclaration elemDecl, PropertyDescriptor descriptor,
CoordinateReferenceSystem crs) {
if (descriptor.getUserData().get("substitutionGroup") != null) {
// this has been done before
return;
}
List<AttributeDescriptor> substitutionGroup = new ArrayList<AttributeDescriptor>();
descriptor.getUserData().put("substitutionGroup", substitutionGroup);
int minOccurs = Schemas.getMinOccurs(container, elemDecl);
int maxOccurs = Schemas.getMaxOccurs(container, elemDecl);
boolean nillable = elemDecl.isNillable();
Iterator substitutions = elemDecl.getSubstitutionGroup().iterator();
XSDElementDeclaration sub;
while (substitutions.hasNext()) {
sub = (XSDElementDeclaration) substitutions.next();
if (!(sub.getName().equals(elemDecl.getName()))
|| !(sub.getTargetNamespace().equals(elemDecl.getTargetNamespace()))) {
Name elemName = Types.typeName(sub.getTargetNamespace(), sub.getName());
AttributeType type = getTypeOf(sub, crs);
if (type != null) {
substitutionGroup.add(createAttributeDescriptor(type, crs, elemName, minOccurs,
maxOccurs, nillable, null));
}
}
}
XSDTypeDefinition typeDef = elemDecl.getType();
if (typeDef instanceof XSDComplexTypeDefinition) {
Name typeName = Types.typeName(typeDef.getTargetNamespace(), typeDef.getName());
AttributeType attType = typeRegistry.get(typeName);
if (!processingTypes.contains(typeName)) {
// ignore processingTypes to avoid endless recursion
if (attType == null || attType instanceof AbstractLazyComplexTypeImpl) {
// type is not yet registered or it's a lazy type from foundation types
// recreate lazy type to ensure everything is loaded
// it will eventually call this method so substitution groups will be set then
LOGGER.finest("Creating attribute type " + typeName);
createType(typeName, typeDef, crs, false);
LOGGER.finest("Registering attribute type " + typeName);
} else if (attType instanceof ComplexType) {
// ensure substitution groups are set for children including non lazy foundation
// types
ComplexType complexType = (ComplexType) attType;
Collection<PropertyDescriptor> children = complexType.getDescriptors();
List<XSDParticle> childParticles = Schemas.getChildElementParticles(typeDef,
true);
for (XSDParticle particle : childParticles) {
XSDElementDeclaration element = (XSDElementDeclaration) particle
.getContent();
if (element.isElementDeclarationReference()) {
element = element.getResolvedElementDeclaration();
}
PropertyDescriptor childDesc = null;
for (PropertyDescriptor desc : children) {
if (desc.getName().getLocalPart().equals(element.getName())
&& desc.getName().getNamespaceURI()
.equals(element.getTargetNamespace())) {
childDesc = desc;
break;
}
}
if (childDesc != null) {
setSubstitutionGroup((XSDComplexTypeDefinition) typeDef, element,
childDesc, crs);
}
}
}
}
}
}
private XSDTypeDefinition getTypeDefinition(Name typeName) {
QName qName = Types.toQName(typeName);
XSDTypeDefinition typeDefinition = null;
for (SchemaIndex schemaIndex : schemas) {
typeDefinition = schemaIndex.getTypeDefinition(qName);
if (typeDefinition != null) {
break;
}
}
if (typeDefinition == null) {
throw new IllegalArgumentException("XSD type definition not found in schemas: " + qName);
}
return typeDefinition;
}
private void register(AttributeDescriptor descriptor) {
Name name = descriptor.getName();
descriptorRegistry.put(name, descriptor);
}
private void register(AttributeType type, boolean anonymous) {
Name name = type.getName();
Object old;
if (anonymous) {
old = anonTypeRegistry.put(name, type);
} else {
old = typeRegistry.put(name, type);
}
if (old != null) {
LOGGER.fine(type.getName() + " replaced by new value.");
}
}
public void register(AttributeType type) {
register(type, false);
}
private AttributeDescriptor createAttributeDescriptor(final XSDComplexTypeDefinition container,
final XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs) {
int minOccurs = container == null ? 0 : Schemas.getMinOccurs(container, elemDecl);
int maxOccurs = container == null ? Integer.MAX_VALUE : Schemas.getMaxOccurs(container,
elemDecl);
return createAttributeDescriptor(elemDecl, minOccurs, maxOccurs, crs);
}
private AttributeDescriptor createAttributeDescriptor(AttributeType type,
CoordinateReferenceSystem crs, Name elemName, int minOccurs, int maxOccurs,
boolean nillable, Object defaultValue) {
AttributeDescriptor descriptor = null;
if (maxOccurs == -1) {
// this happens when maxOccurs is set to "unbounded"
maxOccurs = Integer.MAX_VALUE;
}
if (!(type instanceof AttributeTypeProxy)
&& (Geometry.class.isAssignableFrom(type.getBinding()) || CurvedGeometry.class
.isAssignableFrom(type.getBinding()))) {
// create geometry descriptor with the simple feature type CRS
GeometryType geomType = new GeometryTypeImpl(type.getName(), type.getBinding(), crs,
type.isIdentified(), type.isAbstract(), type.getRestrictions(),
type.getSuper(), type.getDescription());
descriptor = typeFactory.createGeometryDescriptor(geomType, elemName, minOccurs,
maxOccurs, nillable, defaultValue);
} else {
descriptor = typeFactory.createAttributeDescriptor(type, elemName, minOccurs,
maxOccurs, nillable, defaultValue);
}
return descriptor;
}
private AttributeDescriptor createAttributeDescriptor(final XSDElementDeclaration elemDecl,
int minOccurs, int maxOccurs, CoordinateReferenceSystem crs) {
String targetNamespace = elemDecl.getTargetNamespace();
String name = elemDecl.getName();
Name elemName = Types.typeName(targetNamespace, name);
AttributeType type = getTypeOf(elemDecl, crs);
boolean nillable = elemDecl.isNillable();
Object defaultValue = null;
AttributeDescriptor descriptor = createAttributeDescriptor(type, crs, elemName, minOccurs,
maxOccurs, nillable, defaultValue);
descriptor.getUserData().put(XSDElementDeclaration.class, elemDecl);
return descriptor;
}
/**
* If the type of elemDecl is annonymous creates a new type with the same name than the atrribute and returns it. If it is not anonymous, looks it
* up on the registry and in case the type does not exists in the registry uses a proxy.
*
* @param elemDecl
* @return
*/
private AttributeType getTypeOf(XSDElementDeclaration elemDecl, CoordinateReferenceSystem crs) {
XSDTypeDefinition typeDefinition;
// TODO REVISIT, I'm not sure this is the way to find out if the
// element's type is defined in line (an thus no need to register it
// as a global type)
if (elemDecl.isElementDeclarationReference()) {
elemDecl = elemDecl.getResolvedElementDeclaration();
}
boolean hasToBeRegistered = false;
typeDefinition = elemDecl.getAnonymousTypeDefinition();
if (typeDefinition == null) {
// anonymous types already has type definition inline in the element
// so the handling is different
hasToBeRegistered = true;
typeDefinition = elemDecl.getTypeDefinition();
}
if (typeDefinition == null) {
// last resort.. look in the lazy schemas
QName qname = Types.toQName(Types.typeName(elemDecl.getTargetNamespace(),
elemDecl.getName()));
for (SchemaIndex schemaIndex : schemas) {
elemDecl = schemaIndex.getElementDeclaration(qname);
if (elemDecl != null) {
break;
}
}
if (elemDecl != null) {
if (elemDecl.isElementDeclarationReference()) {
elemDecl = elemDecl.getResolvedElementDeclaration();
}
typeDefinition = elemDecl.getAnonymousTypeDefinition();
if (typeDefinition == null) {
typeDefinition = elemDecl.getTypeDefinition();
}
}
}
if (typeDefinition == null) {
String msg = "The element declaration " + elemDecl.getTargetNamespace() + "#"
+ elemDecl.getName()
+ " has a null type definition, can't continue, fix it on the schema";
LOGGER.warning(msg);
throw new NoSuchElementException(msg);
}
AttributeType type;
if (hasToBeRegistered) {
String targetNamespace = typeDefinition.getTargetNamespace();
String name = typeDefinition.getName();
Name typeName = Types.typeName(targetNamespace, name);
type = getAttributeType(typeName, typeDefinition, crs);
if (type == null) {
type = createType(typeName, typeDefinition, crs, false);
}
} else {
String name = elemDecl.getName();
String targetNamespace = elemDecl.getTargetNamespace();
Name overrideName = Types.typeName(targetNamespace, name);
type = createType(overrideName, typeDefinition, crs, true);
}
return type;
}
private AttributeType createProxiedType(final Name assignedName,
final XSDTypeDefinition typeDefinition, Map typeRegistry) {
AttributeType type;
if (null == typeDefinition.getSimpleType()
&& typeDefinition instanceof XSDComplexTypeDefinition) {
if (helper.isFeatureType(typeDefinition)) {
type = new FeatureTypeProxy(assignedName, typeRegistry);
} else {
type = new ComplexTypeProxy(assignedName, typeRegistry);
}
} else {
if (helper.isGeometryType(typeDefinition)) {
type = new GeometryTypeProxy(assignedName, typeRegistry);
} else {
type = new AttributeTypeProxy(assignedName, typeRegistry);
}
}
return type;
}
/**
* Creates an {@link AttributeType} that matches the xsd type definition as much as possible.
* <p>
* The original type definition given by the {@link XSDTypeDefinition} is kept as AttributeType's metadata stored as a "user data" property using
* <code>XSDTypeDefinition.class</code> as key.
* </p>
* <p>
* If it is a complex attribute, it will contain all the properties declared in the <code>typeDefinition</code>, as well as all the properties
* declared in its super types.
* </p>
* TODO: handle the case where the extension mechanism is restriction.
*
* @param assignedName
* @param typeDefinition
* @return
*/
private AttributeType createType(final Name assignedName,
final XSDTypeDefinition typeDefinition, CoordinateReferenceSystem crs, boolean anonymous) {
AttributeType attType;
// /////////
if (processingTypes.contains(assignedName)) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Recursion found for type " + assignedName + ". Proxying it.");
}
attType = createProxiedType(assignedName, typeDefinition, anonymous ? anonTypeRegistry
: typeRegistry);
return attType;
}
processingTypes.push(assignedName);
// //////////
final XSDTypeDefinition baseType = typeDefinition.getBaseType();
AttributeType superType = null;
if (baseType != null) {
String targetNamespace = baseType.getTargetNamespace();
String name = baseType.getName();
if (name != null) {
Name baseTypeName = new NameImpl(targetNamespace, name);
superType = getAttributeType(baseTypeName, baseType, crs);
}
} else {
LOGGER.fine(assignedName + " has no super type");
}
if (typeDefinition instanceof XSDComplexTypeDefinition) {
XSDComplexTypeDefinition complexTypeDef;
complexTypeDef = (XSDComplexTypeDefinition) typeDefinition;
boolean includeParents = true;
List<XSDElementDeclaration> children = Schemas.getChildElementDeclarations(
typeDefinition, includeParents);
final Collection<PropertyDescriptor> schema = new ArrayList<PropertyDescriptor>(
children.size());
XSDElementDeclaration childDecl;
AttributeDescriptor descriptor;
for (Iterator it = children.iterator(); it.hasNext();) {
childDecl = (XSDElementDeclaration) it.next();
try {
descriptor = createAttributeDescriptor(complexTypeDef, childDecl, crs);
} catch (NoSuchElementException e) {
String msg = "Failed to create descriptor for '"
+ childDecl.getTargetNamespace() + "#" + childDecl.getName()
+ " from container '" + typeDefinition.getTargetNamespace() + "#"
+ typeDefinition.getName() + "'";
NoSuchElementException nse = new NoSuchElementException(msg);
nse.initCause(e);
throw nse;
}
schema.add(descriptor);
}
if (includeAttributes) {
for (XSDAttributeUse attgcontent : complexTypeDef.getAttributeUses()) {
XSDAttributeDeclaration att = attgcontent.getContent();
descriptor = createAttributeDescriptor (getXmlAttributeType(), null, new NameImpl(null, "@" + att.getName()), 0,1, false,null);
schema.add(descriptor);
}
}
// set substitution group for descriptors here
for (XSDElementDeclaration elemDecl : children) {
if (elemDecl.isElementDeclarationReference()) {
elemDecl = elemDecl.getResolvedElementDeclaration();
}
PropertyDescriptor att = null;
for (PropertyDescriptor desc : schema) {
if (desc.getName().getLocalPart().equals(elemDecl.getName())
&& desc.getName().getNamespaceURI()
.equals(elemDecl.getTargetNamespace())) {
att = desc;
break;
}
}
setSubstitutionGroup(complexTypeDef, elemDecl, att, crs);
}
attType = createComplexAttributeType(assignedName, schema, complexTypeDef, superType);
} else {
Class<?> binding = String.class;
boolean isIdentifiable = false;
boolean isAbstract = false;
List<Filter> restrictions = Collections.emptyList();
InternationalString description = null;
attType = typeFactory.createAttributeType(assignedName, binding, isIdentifiable,
isAbstract, restrictions, superType, description);
}
attType.getUserData().put(XSDTypeDefinition.class, typeDefinition);
processingTypes.pop();
// even if the type is anonymous, it still has to be registered somewhere because
// it's needed for the proxied types to find them. That's why we have 2 registries,
// typeRegistry
// and anonTypeRegistry. TypeRegistry is the global one, since anonymous types are meant to
// be
// local and shouldn't be searchable.
register(attType, anonymous);
return attType;
}
/**
* NOTE: to be called only by {@link #createType(Name, XSDTypeDefinition)}
*
* @param assignedName
* @param schema
* @param typeDefinition
* @param superType
* @return
*/
private AttributeType createComplexAttributeType(final Name assignedName,
final Collection<PropertyDescriptor> schema,
final XSDComplexTypeDefinition typeDefinition, final AttributeType superType) {
boolean isAbstract = false;// TODO
List<Filter> restrictions = Collections.emptyList();
InternationalString description = null; // TODO
AttributeType type;
if (helper.isFeatureType(typeDefinition)) {
type = typeFactory.createFeatureType(assignedName, schema, null, isAbstract,
restrictions, superType, description);
} else {
boolean isIdentifiable = helper
.isIdentifiable(typeDefinition);
type = typeFactory.createComplexType(assignedName, schema, isIdentifiable, isAbstract,
restrictions, superType, description);
}
return type;
}
/**
* Caches the basic types
*/
private static Map<Name, AttributeType> FOUNDATION_TYPES = new HashMap<Name, AttributeType>();
private void createFoundationTypes() {
synchronized (FOUNDATION_TYPES) {
if (!FOUNDATION_TYPES.isEmpty()) {
typeRegistry.putAll(FOUNDATION_TYPES);
return;
}
importSchema(new XSSchema());
for (Schema schema : helper.getSchemas()) {
importSchema(schema);
}
for (Configuration config : helper.getConfigurations()) {
addSchemas(Schemas.findSchemas(config));
}
FOUNDATION_TYPES.putAll(typeRegistry);
}
}
@SuppressWarnings("unchecked")
protected void importSchema(Schema schema) {
for (Iterator it = schema.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Entry) it.next();
Name key = (Name) entry.getKey();
Object value = entry.getValue();
if (typeRegistry.containsKey(key)) {
LOGGER.finer("Ignoring " + key + " as it already exists. type "
+ value.getClass().getName());
} else {
LOGGER.finer("Importing " + key + " of type " + value.getClass().getName());
if (value instanceof AttributeType) {
AttributeType type = (AttributeType) value;
register(type, false);
} else if (value instanceof AttributeDescriptor) {
AttributeDescriptor descriptor = (AttributeDescriptor) value;
register(descriptor);
}
}
}
LOGGER.fine("Schema " + schema.getURI() + " imported successfully");
}
public AttributeType getXmlAttributeType() {
if (XMLATTRIBUTE_TYPE == null) {
XMLATTRIBUTE_TYPE = typeFactory.createAttributeType(new NameImpl(null, "@attribute"), String.class, false,
false, Collections.<Filter>emptyList(), null, null);
}
return XMLATTRIBUTE_TYPE;
}
}