/** * Copyright (c) Codice Foundation * <p> * This 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, either version 3 of the * License, or any later version. * <p> * This program 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. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. **/ package org.codice.ddf.spatial.ogc.wfs.catalog.common; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.apache.commons.lang.StringUtils; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.XmlSchemaComplexContent; import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension; import org.apache.ws.commons.schema.XmlSchemaComplexType; import org.apache.ws.commons.schema.XmlSchemaContent; import org.apache.ws.commons.schema.XmlSchemaElement; import org.apache.ws.commons.schema.XmlSchemaParticle; import org.apache.ws.commons.schema.XmlSchemaSequence; import org.apache.ws.commons.schema.XmlSchemaSequenceMember; import org.apache.ws.commons.schema.XmlSchemaSimpleType; import org.apache.ws.commons.schema.XmlSchemaType; import org.apache.ws.commons.schema.constants.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.AttributeType; import ddf.catalog.data.impl.AttributeDescriptorImpl; import ddf.catalog.data.impl.BasicTypes; import ddf.catalog.data.impl.MetacardTypeImpl; import ddf.catalog.data.impl.types.ContactAttributes; import ddf.catalog.data.impl.types.CoreAttributes; import ddf.catalog.data.impl.types.DateTimeAttributes; import ddf.catalog.data.impl.types.LocationAttributes; import ddf.catalog.data.impl.types.MediaAttributes; import ddf.catalog.data.impl.types.ValidationAttributes; public class FeatureMetacardType extends MetacardTypeImpl { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(FeatureMetacardType.class); private transient List<String> properties = new ArrayList<>(); private transient List<String> textualProperties = new ArrayList<>(); private transient List<String> gmlProperties = new ArrayList<>(); private transient List<String> temporalProperties = new ArrayList<>(); private transient QName featureType; private transient String propertyPrefix; private transient List<String> nonQueryableProperties; private transient String gmlNamespace; private static final String EXT_PREFIX = "ext."; public FeatureMetacardType(XmlSchema schema, final QName featureType, List<String> nonQueryableProperties, String gmlNamespace) { super(featureType.getLocalPart(), (Set<AttributeDescriptor>) null); addAllDescriptors(); this.featureType = featureType; this.nonQueryableProperties = nonQueryableProperties; this.propertyPrefix = EXT_PREFIX + getName() + "."; this.gmlNamespace = gmlNamespace; if (schema != null) { processXmlSchema(schema); } else { throw new IllegalArgumentException( "FeatureTypeMetacard cannot be created with a null Schema."); } } /** * we don't want to expose these in a query interface ie wfs endpoint, so we need to create new * attributes for each and set them to stored = false note: indexed is being used to determine * whether or not to query certain wfs fields so it did not seem appropriate to hide those * fields from the endpoint schema */ private void addDescriptors(Set<AttributeDescriptor> attrDescriptors) { for (AttributeDescriptor descriptor : attrDescriptors) { AttributeDescriptorImpl basicAttributeDescriptor = (AttributeDescriptorImpl) descriptor; AttributeDescriptor attributeDescriptor = new AttributeDescriptorImpl( basicAttributeDescriptor.getName(), false, false, basicAttributeDescriptor.isTokenized(), basicAttributeDescriptor.isMultiValued(), basicAttributeDescriptor.getType()); descriptors.add(attributeDescriptor); } } private void addAllDescriptors() { addDescriptors(new CoreAttributes().getAttributeDescriptors()); addDescriptors(new ContactAttributes().getAttributeDescriptors()); addDescriptors(new LocationAttributes().getAttributeDescriptors()); addDescriptors(new MediaAttributes().getAttributeDescriptors()); addDescriptors(new DateTimeAttributes().getAttributeDescriptors()); addDescriptors(new ValidationAttributes().getAttributeDescriptors()); addDescriptors(new MediaAttributes().getAttributeDescriptors()); } public String getName() { return featureType.getLocalPart(); } public String getPrefix() { return featureType.getPrefix(); } public String getNamespaceURI() { return featureType.getNamespaceURI(); } public QName getFeatureType() { return featureType; } private void processXmlSchema(XmlSchema schema) { Map<QName, XmlSchemaElement> elements = schema.getElements(); Iterator<XmlSchemaElement> xmlSchemaElementIterator = elements.values() .iterator(); while (xmlSchemaElementIterator.hasNext()) { Object o = xmlSchemaElementIterator.next(); XmlSchemaElement element = (XmlSchemaElement) o; XmlSchemaType schemaType = element.getSchemaType(); if (schemaType instanceof XmlSchemaComplexType) { processComplexType(element); } else if (schemaType instanceof XmlSchemaSimpleType) { processSimpleType(element); } } } private void processComplexType(XmlSchemaElement xmlSchemaElement) { if (!processGmlType(xmlSchemaElement)) { XmlSchemaType schemaType = xmlSchemaElement.getSchemaType(); if ((schemaType != null) && (schemaType instanceof XmlSchemaComplexType)) { XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaType; if (complexType.getParticle() != null) { processXmlSchemaParticle(complexType.getParticle()); } else { if (complexType.getContentModel() instanceof XmlSchemaComplexContent) { XmlSchemaContent content = complexType.getContentModel() .getContent(); if (content instanceof XmlSchemaComplexContentExtension) { XmlSchemaComplexContentExtension extension = (XmlSchemaComplexContentExtension) content; processXmlSchemaParticle(extension.getParticle()); } } } } } } private void processXmlSchemaParticle(XmlSchemaParticle particle) { XmlSchemaSequence schemaSequence; if (particle instanceof XmlSchemaSequence) { schemaSequence = (XmlSchemaSequence) particle; List<XmlSchemaSequenceMember> schemaObjectCollection = schemaSequence.getItems(); Iterator<XmlSchemaSequenceMember> iterator = schemaObjectCollection.iterator(); while (iterator.hasNext()) { Object element = iterator.next(); if (element instanceof XmlSchemaElement) { XmlSchemaElement innerElement = ((XmlSchemaElement) element); XmlSchemaType innerEleType = innerElement.getSchemaType(); if (innerEleType instanceof XmlSchemaComplexType) { processComplexType(innerElement); } else if (innerEleType instanceof XmlSchemaSimpleType) { processSimpleType(innerElement); } else if (innerEleType == null) { // Check if this is the GML location Property processGmlType(innerElement); } } } } } private void processSimpleType(XmlSchemaElement xmlSchemaElement) { QName qName = xmlSchemaElement.getSchemaTypeName(); String name = xmlSchemaElement.getName(); AttributeType<?> attributeType = toBasicType(qName); if (attributeType != null) { boolean multiValued = xmlSchemaElement.getMaxOccurs() > 1; descriptors.add(new FeatureAttributeDescriptor(propertyPrefix + name, name, isQueryable(name) /* indexed */, true /* stored */, false /* tokenized */, multiValued, attributeType)); } if (Constants.XSD_STRING.equals(qName)) { textualProperties.add(propertyPrefix + name); } properties.add(propertyPrefix + name); } private Boolean processGmlType(XmlSchemaElement xmlSchemaElement) { QName qName = xmlSchemaElement.getSchemaTypeName(); String name = xmlSchemaElement.getName(); if (qName != null && StringUtils.isNotEmpty(name) && qName.getNamespaceURI() .equals(gmlNamespace) && (qName.getLocalPart() .equals("TimeInstantType") || qName.getLocalPart() .equals("TimePeriodType"))) { LOGGER.debug("Adding temporal property: {}", propertyPrefix + name); temporalProperties.add(propertyPrefix + name); boolean multiValued = xmlSchemaElement.getMaxOccurs() > 1; descriptors.add(new FeatureAttributeDescriptor(propertyPrefix + name, name, isQueryable(name) /* indexed */, true /* stored */, false /* tokenized */, multiValued, BasicTypes.DATE_TYPE)); properties.add(propertyPrefix + name); return true; } if ((qName != null) && qName.getNamespaceURI() .equals(gmlNamespace) && (!StringUtils.isEmpty(name))) { LOGGER.debug("Adding geo property: {}", propertyPrefix + name); gmlProperties.add(propertyPrefix + name); boolean multiValued = xmlSchemaElement.getMaxOccurs() > 1; descriptors.add(new FeatureAttributeDescriptor(propertyPrefix + name, name, isQueryable(name) /* indexed */, true /* stored */, false /* tokenized */, multiValued, BasicTypes.GEO_TYPE)); properties.add(propertyPrefix + name); return true; } return false; } private AttributeType<?> toBasicType(QName qName) { if (Constants.XSD_STRING.equals(qName)) { return BasicTypes.STRING_TYPE; } if (Constants.XSD_DATETIME.equals(qName) || Constants.XSD_DATE.equals(qName)) { return BasicTypes.DATE_TYPE; } if (Constants.XSD_BOOLEAN.equals(qName)) { return BasicTypes.BOOLEAN_TYPE; } if (Constants.XSD_DOUBLE.equals(qName)) { return BasicTypes.DOUBLE_TYPE; } if (Constants.XSD_FLOAT.equals(qName)) { return BasicTypes.FLOAT_TYPE; } if (Constants.XSD_INT.equals(qName)) { return BasicTypes.INTEGER_TYPE; } if (Constants.XSD_LONG.equals(qName)) { return BasicTypes.LONG_TYPE; } if (Constants.XSD_SHORT.equals(qName)) { return BasicTypes.SHORT_TYPE; } // these types are unbounded and unsafe to map to any BasicTypes number values. // Potentially the catalog should support a BigInteger type for these types to map to if (Constants.XSD_INTEGER.equals(qName) || Constants.XSD_POSITIVEINTEGER.equals(qName) || Constants.XSD_NEGATIVEINTEGER.equals(qName) || Constants.XSD_NONPOSITIVEINTEGER.equals(qName) || Constants.XSD_NONNEGATIVEINTEGER.equals(qName)) { return BasicTypes.STRING_TYPE; } return null; } private boolean isQueryable(String propertyName) { return !nonQueryableProperties.contains(propertyName); } public List<String> getTextualProperties() { return textualProperties; } public List<String> getGmlProperties() { return gmlProperties; } public List<String> getProperties() { return properties; } public List<String> getTemporalProperties() { return temporalProperties; } }