/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2009, 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.gml3.bindings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDTypeDefinition;
import org.geotools.feature.NameImpl;
import org.geotools.feature.type.FeatureTypeImpl;
import org.geotools.gml3.XSDIdRegistry;
import org.geotools.util.Converters;
import org.geotools.xlink.XLINK;
import org.geotools.xml.Schemas;
import org.geotools.xs.XS;
import org.geotools.xs.bindings.XSAnyTypeBinding;
import org.opengis.feature.Attribute;
import org.opengis.feature.ComplexAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.type.Name;
import org.opengis.feature.type.PropertyDescriptor;
import org.opengis.filter.identity.Identifier;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
/**
* A replacement for {@link XSAnyTypeBinding} that adds support for {@link ComplexAttribute} and
* related behaviours.
*
* <p>
*
* This binding that searches the substitution group of XSD element children to find properties of a
* complex attribute. This is necessary to support the GML property type pattern, in which a
* property (a property-type type) contains a property that is a member of a substitution group.
* gml:AttributeType is the canonical example of the property type pattern.
*
* <p>
*
* gml:FeaturePropertyType is an example of the property type pattern that has an explicit binding
* {@link FeaturePropertyTypeBinding}, but because an application schema may define more property
* types whose names are not known at compile time, a binding like
* {@link FeaturePropertyTypeBinding} cannot be written. This class exists to handle these
* application-schema-defined property types.
*
* <p>
*
* This class supports the encoding of XML complexType with simpleContent through extraction of a
* simpleContent property, as well as encoding XML attributes stored in the UserData map.
*
* @author Ben Caradoc-Davies, CSIRO Earth Science and Resource Engineering
*/
public class ComplexSupportXSAnyTypeBinding extends XSAnyTypeBinding {
private XSDIdRegistry idSet;
public ComplexSupportXSAnyTypeBinding(XSDIdRegistry idRegistry) {
this.idSet = idRegistry;
}
/**
* @see org.geotools.xml.AbstractComplexBinding#getProperty(java.lang.Object,
* javax.xml.namespace.QName)
*/
@Override
public Object getProperty(Object object, QName name) throws Exception {
if (object instanceof ComplexAttribute) {
ComplexAttribute complex = (ComplexAttribute) object;
Property property = complex.getProperty(toTypeName(name));
if (property != null && !(property instanceof ComplexAttribute)) {
return property.getValue();
}
}
return null;
}
/**
* Convert a {@link QName} to a {@link Name}.
*
* @param name
* @return
*/
private static Name toTypeName(QName name) {
if (XMLConstants.NULL_NS_URI.equals(name.getNamespaceURI())) {
return new NameImpl(name.getLocalPart());
} else {
return new NameImpl(name.getNamespaceURI(), name.getLocalPart());
}
}
/**
* @see org.geotools.xml.AbstractComplexBinding#getProperties(java.lang.Object,
* org.eclipse.xsd.XSDElementDeclaration)
*/
@SuppressWarnings("unchecked")
@Override
public List getProperties(Object object, XSDElementDeclaration element) throws Exception {
List<Object[/* 2 */]> properties = new ArrayList<Object[/* 2 */]>();
XSDTypeDefinition typeDef = element.getTypeDefinition();
boolean isAnyType = typeDef.getName() != null && typeDef.getTargetNamespace() != null
&& typeDef.getName().equals(XS.ANYTYPE.getLocalPart())
&& typeDef.getTargetNamespace().equals(XS.NAMESPACE);
if (isAnyType) {
Collection complexAtts;
if (object instanceof Collection) {
// collection of features
complexAtts = (Collection) object;
} else if (object instanceof ComplexAttribute) {
// get collection of features from this attribute
complexAtts = ((ComplexAttribute) object).getProperties();
} else {
return null;
}
for (Object complex : complexAtts) {
if (complex instanceof ComplexAttribute) {
PropertyDescriptor descriptor = ((Attribute) complex).getDescriptor();
if (descriptor.getUserData() != null) {
Object propertyElement = descriptor.getUserData().get(
XSDElementDeclaration.class);
if (propertyElement != null
&& propertyElement instanceof XSDElementDeclaration) {
XSDParticle substitutedChildParticle = XSDFactory.eINSTANCE
.createXSDParticle();
substitutedChildParticle.setMaxOccurs(descriptor.getMaxOccurs());
substitutedChildParticle.setMinOccurs(descriptor.getMinOccurs());
XSDElementDeclaration wrapper = XSDFactory.eINSTANCE
.createXSDElementDeclaration();
wrapper
.setResolvedElementDeclaration((XSDElementDeclaration) propertyElement);
substitutedChildParticle.setContent(wrapper);
properties.add(new Object[] { substitutedChildParticle, complex });
}
}
}
}
return properties;
}
if (object instanceof ComplexAttribute) {
ComplexAttribute complex = (ComplexAttribute) object;
for (XSDParticle childParticle : (List<XSDParticle>) Schemas.getChildElementParticles(
element.getTypeDefinition(), true)) {
XSDElementDeclaration childElement = (XSDElementDeclaration) childParticle
.getContent();
if (childElement.isElementDeclarationReference()) {
childElement = childElement.getResolvedElementDeclaration();
}
for (XSDElementDeclaration e : (List<XSDElementDeclaration>) childElement
.getSubstitutionGroup()) {
Name name = new NameImpl(e.getTargetNamespace(), e.getName());
Collection<Property> nameProperties = complex.getProperties(name);
if (!nameProperties.isEmpty()) {
// Particle creation stolen from BindingPropertyExtractor.
// I do not know why a wrapper is required; monkey see, monkey do.
// Without the wrapper, get an NPE in BindingPropertyExtractor.
XSDParticle substitutedChildParticle = XSDFactory.eINSTANCE
.createXSDParticle();
substitutedChildParticle.setMaxOccurs(childParticle.getMaxOccurs());
substitutedChildParticle.setMinOccurs(childParticle.getMinOccurs());
XSDElementDeclaration wrapper = XSDFactory.eINSTANCE
.createXSDElementDeclaration();
wrapper.setResolvedElementDeclaration(e);
substitutedChildParticle.setContent(wrapper);
for (Property property : nameProperties) {
/*
* Note : Returning simple feature value is not necessary as it has been
* taken care in the 1st For Loop of BindingPropertyExtractor.java -
* List properties(Object, XSDElementDeclaration) method
*/
if (property instanceof ComplexAttribute) {
properties.add(new Object[] { substitutedChildParticle, property });
}
}
}
}
}
return properties;
}
return null;
}
/**
* Check if the complex attribute contains a feature which id is pre-existing in the document.
* If it's true, make sure it's only encoded as an xlink:href to the existing id.
*
* @param value
* The complex attribute value
* @param att
* The complex attribute itself
*/
private void checkXlinkHref(Object value, ComplexAttribute att) {
if (value != null && value instanceof ComplexAttribute) {
ComplexAttribute object = (ComplexAttribute) value;
// Only worry about features for now, as non feature types don't get ids encoded yet.
// See GEOS-3738. To encode xlink:href to an id that doesn't exist in the doc is wrong
if (!(object.getType() instanceof FeatureTypeImpl)) {
// we are checking the type, not the object as FeatureImpl, because they could still
// be non-features that are constructed as features for the purpose of feature
// chaining.
return;
}
Identifier ident = object.getIdentifier();
if (ident == null) {
return;
}
String id = Converters.convert(ident.getID(), String.class);
if (idSet.idExists(id)) {
// XSD type ids can only appear once in the same document, otherwise the document is
// not schema valid. Attributes of the same ids should be encoded as xlink:href to
// the existing attribute.
Object clientProperties = att.getUserData().get(Attributes.class);
Map<Name, Object> map = null;
if (clientProperties == null) {
map = new HashMap<Name, Object>();
att.getUserData().put(Attributes.class, map);
} else {
map = (Map<Name, Object>) clientProperties;
}
map.put(toTypeName(XLINK.HREF), "#" + id.toString());
// make sure the value is not encoded
att.setValue(Collections.emptyList());
}
}
return;
}
/**
* @see org.geotools.xml.AbstractComplexBinding#encode(java.lang.Object, org.w3c.dom.Document,
* org.w3c.dom.Element)
*/
@Override
public Element encode(Object object, Document document, Element value) throws Exception {
if (object instanceof ComplexAttribute) {
ComplexAttribute complex = (ComplexAttribute) object;
if (complex.getProperties().size() == 1) {
Property prop = complex.getProperties().iterator().next();
checkXlinkHref(prop, complex);
}
GML3EncodingUtils.encodeClientProperties(complex, value);
GML3EncodingUtils.encodeSimpleContent(complex, document, value);
}
return value;
}
}