/**
* Copyright (c) Codice Foundation
*
* 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.
*
* 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.csw.catalog.source;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.common.util.CollectionUtils;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants.BinarySpatialOperand;
import org.codice.ddf.spatial.ogc.csw.catalog.common.CswJAXBElementProvider;
import org.jvnet.ogc.gml.v_3_1_1.jts.JTSToGML311GeometryConverter;
import org.jvnet.ogc.gml.v_3_1_1.jts.MarshallerImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import net.opengis.filter.v_1_1_0.AbstractIdType;
import net.opengis.filter.v_1_1_0.BBOXType;
import net.opengis.filter.v_1_1_0.BinaryComparisonOpType;
import net.opengis.filter.v_1_1_0.BinaryLogicOpType;
import net.opengis.filter.v_1_1_0.BinarySpatialOpType;
import net.opengis.filter.v_1_1_0.DistanceBufferType;
import net.opengis.filter.v_1_1_0.DistanceType;
import net.opengis.filter.v_1_1_0.FeatureIdType;
import net.opengis.filter.v_1_1_0.FilterType;
import net.opengis.filter.v_1_1_0.LiteralType;
import net.opengis.filter.v_1_1_0.LowerBoundaryType;
import net.opengis.filter.v_1_1_0.ObjectFactory;
import net.opengis.filter.v_1_1_0.PropertyIsBetweenType;
import net.opengis.filter.v_1_1_0.PropertyIsLikeType;
import net.opengis.filter.v_1_1_0.PropertyIsNullType;
import net.opengis.filter.v_1_1_0.PropertyNameType;
import net.opengis.filter.v_1_1_0.UnaryLogicOpType;
import net.opengis.filter.v_1_1_0.UpperBoundaryType;
import net.opengis.gml.v_3_1_1.AbstractGeometryType;
import net.opengis.gml.v_3_1_1.DirectPositionType;
import net.opengis.gml.v_3_1_1.EnvelopeType;
/**
* Factory for creating OGC filters.
*/
public class CswFilterFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(CswFilterFactory.class);
// Regex to match coords in WKT
private static final Pattern COORD_PATTERN = Pattern
.compile("-?\\.?\\d+(\\.?\\d+)?\\s-?\\.?\\d+(\\.?\\d+)?");
private static final JAXBContext JAXB_CONTEXT = initJaxbContext();
private ObjectFactory filterObjectFactory = new ObjectFactory();
private net.opengis.gml.v_3_1_1.ObjectFactory gmlObjectFactory = new net.opengis.gml.v_3_1_1.ObjectFactory();
private boolean isLonLatOrder;
private boolean isSetUsePosList;
/**
* Constructor for CswFilterFactory.
*
* @param isLonLatOrder
* True if coordinate order is longitude, latitude; False, otherwise.
*
* @param isSetUsePosList
* True if a single <posList> element, rather than a set of <pos>
* elements, should be used in LinearRings when constructing XML
* Filter strings.
*/
public CswFilterFactory(boolean isLonLatOrder, boolean isSetUsePosList) {
this.isLonLatOrder = isLonLatOrder;
this.isSetUsePosList = isSetUsePosList;
}
private static JAXBContext initJaxbContext() {
JAXBContext jaxbContext = null;
// JAXB context path
// "net.opengis.cat.csw.v_2_0_2:net.opengis.filter.v_1_1_0:net.opengis.gml.v_3_1_1:net.opengis.ows.v_1_0_0"
String contextPath = StringUtils
.join(new String[] {CswConstants.OGC_CSW_PACKAGE, CswConstants.OGC_FILTER_PACKAGE,
CswConstants.OGC_GML_PACKAGE, CswConstants.OGC_OWS_PACKAGE}, ":");
try {
LOGGER.debug("Creating JAXB context with context path: {}.", contextPath);
jaxbContext = JAXBContext
.newInstance(contextPath, CswJAXBElementProvider.class.getClassLoader());
LOGGER.debug(jaxbContext.toString());
} catch (JAXBException e) {
LOGGER.error("Unable to create JAXB context using contextPath: {}.", contextPath, e);
}
return jaxbContext;
}
public FilterType buildNotFilter(FilterType filter) {
FilterType returnFilter = new FilterType();
if (filter == null) {
return returnFilter;
}
UnaryLogicOpType notType = new UnaryLogicOpType();
if (filter.isSetComparisonOps()) {
notType.setComparisonOps(filter.getComparisonOps());
} else if (filter.isSetLogicOps()) {
notType.setLogicOps(filter.getLogicOps());
} else if (filter.isSetSpatialOps()) {
notType.setSpatialOps(filter.getSpatialOps());
} else {
return returnFilter;
}
returnFilter.setLogicOps(filterObjectFactory.createNot(notType));
return returnFilter;
}
public FilterType buildAndFilter(List<FilterType> filters) {
return buildAndOrFilter(filters, filterObjectFactory.createAnd(new BinaryLogicOpType()));
}
public FilterType buildOrFilter(List<FilterType> filters) {
return buildAndOrFilter(filters, filterObjectFactory.createOr(new BinaryLogicOpType()));
}
public FilterType buildPropertyIsBetweenFilter(String propertyName, Object lowerBoundary,
Object upperBoundary) {
FilterType filter = new FilterType();
filter.setComparisonOps(
createPropertyIsBetween(propertyName, lowerBoundary, upperBoundary));
return filter;
}
public FilterType buildPropertyIsNullFilter(String propertyName) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsNull(propertyName));
return filter;
}
public FilterType buildPropertyIsEqualToFilter(String propertyName, Object literal,
boolean isCaseSensitive) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsEqualTo(propertyName, literal, isCaseSensitive));
return filter;
}
public FilterType buildPropertyIsNotEqualToFilter(String propertyName, Object literal,
boolean isCaseSensitive) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsNotEqualTo(propertyName, literal, isCaseSensitive));
return filter;
}
public FilterType buildPropertyIsLikeFilter(String propertyName, Object literal) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsLike(propertyName, literal));
return filter;
}
public FilterType buildPropertyIsGreaterThanFilter(String propertyName, Object literal) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsGreaterThan(propertyName, literal));
return filter;
}
public FilterType buildPropertyIsGreaterThanOrEqualToFilter(String propertyName,
Object literal) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsGreaterThanOrEqualTo(propertyName, literal));
return filter;
}
public FilterType buildPropertyIsLessThanFilter(String propertyName, Object literal) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsLessThan(propertyName, literal));
return filter;
}
public FilterType buildPropertyIsLessThanOrEqualToFilter(String propertyName, Object literal) {
FilterType filter = new FilterType();
filter.setComparisonOps(createPropertyIsLessThanOrEqualTo(propertyName, literal));
return filter;
}
public FilterType buildContainsGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createContainsType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildIntersectsGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createIntersectsType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildCrossesGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createCrossesType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildTouchesGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createTouchesType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildOverlapsGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createOverlapsType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildBeyondGeospatialFilter(String propertyName, String wkt,
double distance) {
FilterType filter = new FilterType();
filter.setSpatialOps(createBeyondType(propertyName, wkt, distance));
return filter;
}
public FilterType buildDisjointGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createDisjointType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildDWithinGeospatialFilter(String propertyName, String wkt,
double distance) {
FilterType filter = new FilterType();
filter.setSpatialOps(createDWithinType(propertyName, wkt, distance));
return filter;
}
public FilterType buildWithinGeospatialFilter(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
FilterType filter = new FilterType();
filter.setSpatialOps(createWithinType(propertyName, wkt, geometryOrEnvelope));
return filter;
}
public FilterType buildBBoxGeospatialFilter(String propertyName, String wkt) {
FilterType filter = new FilterType();
filter.setSpatialOps(createBBoxType(propertyName, wkt));
return filter;
}
private FilterType buildAndOrFilter(List<FilterType> filters,
JAXBElement<BinaryLogicOpType> binaryLogicFilter) {
if (filters.isEmpty()) {
throw new UnsupportedOperationException(
"No valid filters found in the list of filters.");
}
removeEmptyFilters(filters);
Set<String> featureIds = getFeatureIds(filters);
if (!CollectionUtils.isEmpty(featureIds)) {
return buildFeatureIdFilter(featureIds);
}
if (filters.size() == 1) {
return filters.get(0);
}
for (FilterType filter : filters) {
if (filter.isSetComparisonOps()) {
binaryLogicFilter.getValue().getComparisonOpsOrSpatialOpsOrLogicOps()
.add(filter.getComparisonOps());
} else if (filter.isSetLogicOps()) {
binaryLogicFilter.getValue().getComparisonOpsOrSpatialOpsOrLogicOps()
.add(filter.getLogicOps());
} else if (filter.isSetSpatialOps()) {
binaryLogicFilter.getValue().getComparisonOpsOrSpatialOpsOrLogicOps()
.add(filter.getSpatialOps());
}
}
FilterType returnFilter = new FilterType();
returnFilter.setLogicOps(binaryLogicFilter);
return returnFilter;
}
private void removeEmptyFilters(List<FilterType> filters) {
List<FilterType> filtersToBeRemoved = new ArrayList<FilterType>(filters.size());
for (FilterType filter : filters) {
if (filter == null) {
throw new UnsupportedOperationException("Invalid filter specified!");
} else if (!isFilterSet(filter)) {
filtersToBeRemoved.add(filter);
}
}
// remove any null filters or those marked for removal
filters.removeAll(filtersToBeRemoved);
// add an empty filter back in if the list is empty
if (filters.isEmpty()) {
filters.add(new FilterType());
}
}
private boolean isFilterSet(FilterType filter) {
return (filter.isSetComparisonOps() || filter.isSetId() || filter.isSetLogicOps() || filter
.isSetSpatialOps());
}
private Set<String> getFeatureIds(List<FilterType> filters) {
Set<String> ids = new HashSet<String>();
if (!CollectionUtils.isEmpty(filters)) {
boolean isFeatureIdFilter = filters.get(0) != null && filters.get(0).isSetId();
for (FilterType filter : filters) {
if ((filter != null && filter.isSetId()) != isFeatureIdFilter) {
throw new UnsupportedOperationException(
"Query with mix of FeatureID and non-FeatureID queries not supported.");
}
if (isFeatureIdFilter) {
Iterator<JAXBElement<? extends AbstractIdType>> iter = filter.getId()
.iterator();
while (iter.hasNext()) {
@SuppressWarnings("unchecked")
FeatureIdType idType = ((JAXBElement<FeatureIdType>) iter.next())
.getValue();
ids.add(idType.getFid());
}
}
}
}
return ids;
}
private FilterType buildFeatureIdFilter(Set<String> ids) {
FilterType filterType = new FilterType();
for (String id : ids) {
filterType.getId().add(createFeatureIdFilter(id));
}
return filterType;
}
private JAXBElement<FeatureIdType> createFeatureIdFilter(final String id) {
FeatureIdType featureIdType = new FeatureIdType();
featureIdType.setFid(id);
return filterObjectFactory.createFeatureId(featureIdType);
}
private JAXBElement<BinarySpatialOpType> createIntersectsType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createIntersects(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<BinarySpatialOpType> createCrossesType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createCrosses(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<DistanceBufferType> createBeyondType(String propertyName, String wkt,
double distance) {
wkt = convertWktToLatLonOrdering(wkt);
Geometry geometry = getGeometryFromWkt(wkt);
JAXBElement<? extends AbstractGeometryType> geometryJaxbElement = convertGeometry(geometry);
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
DistanceType distanceType = createDistanceType(distance, CswConstants.METERS);
DistanceBufferType distanceBufferType = createDistanceBufferType(propertyNameType,
geometryJaxbElement, distanceType);
return filterObjectFactory.createBeyond(distanceBufferType);
}
private JAXBElement<BinarySpatialOpType> createDisjointType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createDisjoint(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<DistanceBufferType> createDWithinType(String propertyName, String wkt,
double distance) {
wkt = convertWktToLatLonOrdering(wkt);
Geometry geometry = getGeometryFromWkt(wkt);
JAXBElement<? extends AbstractGeometryType> geometryJaxbElement = convertGeometry(geometry);
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
DistanceType distanceType = createDistanceType(distance, CswConstants.METERS);
DistanceBufferType distanceBufferType = createDistanceBufferType(propertyNameType,
geometryJaxbElement, distanceType);
return filterObjectFactory.createDWithin(distanceBufferType);
}
private JAXBElement<BinarySpatialOpType> createContainsType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createContains(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<BinarySpatialOpType> createTouchesType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createTouches(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<BinarySpatialOpType> createOverlapsType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory.createOverlaps(
createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
private JAXBElement<BinarySpatialOpType> createWithinType(String propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
PropertyNameType propertyNameType = createPropertyNameType(propertyName);
return filterObjectFactory
.createWithin(createBinarySpatialOpType(propertyNameType, wkt, geometryOrEnvelope));
}
@SuppressWarnings("unchecked")
private JAXBElement<? extends AbstractGeometryType> convertGeometry(Geometry geometry) {
geometry.setUserData(CswConstants.SRS_NAME);
JAXBElement<? extends AbstractGeometryType> abstractGeometry = null;
try {
Map<String, String> geoConverterProps = new HashMap<String, String>();
geoConverterProps
.put(CswJTSToGML311GeometryConverter.USE_POS_LIST_GEO_CONVERTER_PROP_KEY,
String.valueOf(this.isSetUsePosList));
JTSToGML311GeometryConverter converter = new CswJTSToGML311GeometryConverter(
geoConverterProps);
Marshaller marshaller = new MarshallerImpl(JAXB_CONTEXT.createMarshaller(), converter);
StringWriter writer = new StringWriter();
marshaller.marshal(geometry, writer);
String xmlGeo = writer.toString();
LOGGER.debug("Geometry as XML: {}", xmlGeo);
Reader reader = new StringReader(xmlGeo);
Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
Object object = unmarshaller.unmarshal(reader);
LOGGER.debug("Unmarshalled as => {}", object);
if (object instanceof JAXBElement) {
abstractGeometry = (JAXBElement<? extends AbstractGeometryType>) object;
} else {
LOGGER.error(
"Unable to cast to JAXBElement<? extends AbstractGeometryType>. Object is of type [{}].",
object.getClass().getName());
}
} catch (JAXBException e) {
LOGGER.error("Unable to unmarshal geometry [{}]", geometry.getClass().getName(), e);
}
return abstractGeometry;
}
private JAXBElement<BBOXType> createBBoxType(String propertyName, String wkt) {
BBOXType bboxType = new BBOXType();
JAXBElement<EnvelopeType> envelope = createEnvelopeType(wkt);
bboxType.setEnvelope(envelope);
bboxType.setPropertyName(createPropertyNameType(propertyName));
return filterObjectFactory.createBBOX(bboxType);
}
private DistanceType createDistanceType(double distance, String units) {
DistanceType distanceType = new DistanceType();
distanceType.setUnits(units);
/**
* With the original filter-v_1_1_0-schema_1.1.0.jar, we were unable to set content (the
* actual distance value) on DistanceType. The attribute mixed="true" is missing from the
* complexType DistanceType in filter.xsd. This prevents Beyond and DWithin from working. To
* correct this, we fixed filter.xsd and rebuilt the JAXB bindings (see project
* ogc-filter-v_1_1_0-schema-bindings).
*/
distanceType.setContent(String.valueOf(distance));
return distanceType;
}
@SuppressWarnings("unchecked")
private DistanceBufferType createDistanceBufferType(PropertyNameType propertyName,
JAXBElement<? extends AbstractGeometryType> geometry, DistanceType distance) {
DistanceBufferType distanceBuffer = new DistanceBufferType();
distanceBuffer.setDistance(distance);
distanceBuffer.setGeometry((JAXBElement<AbstractGeometryType>) geometry);
distanceBuffer.setPropertyName(propertyName);
return distanceBuffer;
}
private JAXBElement<EnvelopeType> createEnvelopeType(String wkt) {
EnvelopeType envelopeType = new EnvelopeType();
wkt = convertWktToLatLonOrdering(wkt);
Envelope envelope = getEnvelopeFromWkt(wkt);
if (envelope != null) {
envelopeType.setLowerCorner(
createDirectPositionType(envelope.getMinX(), envelope.getMinY()));
envelopeType.setUpperCorner(
createDirectPositionType(envelope.getMaxX(), envelope.getMaxY()));
}
return this.gmlObjectFactory.createEnvelope(envelopeType);
}
private Envelope getEnvelopeFromWkt(String wkt) {
Geometry geo = getGeometryFromWkt(wkt);
Envelope envelope = geo.getEnvelopeInternal();
return envelope;
}
private DirectPositionType createDirectPositionType(Double x, Double y) {
DirectPositionType directPositionType = new DirectPositionType();
List<Double> coord = new ArrayList<Double>(2);
coord.add(x);
coord.add(y);
directPositionType.setValue(coord);
LOGGER.debug("Created direct position type ({}, {})", x, y);
return directPositionType;
}
private PropertyNameType createPropertyNameType(String propertyName) {
PropertyNameType propertyNameType = new PropertyNameType();
propertyNameType.setContent(Arrays.asList(new Object[] {propertyName}));
return propertyNameType;
}
private JAXBElement<PropertyIsLikeType> createPropertyIsLike(String propertyName,
Object literal) {
PropertyIsLikeType propertyIsLikeType = new PropertyIsLikeType();
propertyIsLikeType.setEscapeChar(CswConstants.ESCAPE);
propertyIsLikeType.setSingleChar(CswConstants.SINGLE_CHAR);
propertyIsLikeType.setWildCard(CswConstants.WILD_CARD);
propertyIsLikeType.setPropertyName(
createPropertyNameType(Arrays.asList(new Object[] {propertyName})).getValue());
propertyIsLikeType.setLiteral(createLiteralType(literal).getValue());
return filterObjectFactory.createPropertyIsLike(propertyIsLikeType);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsEqualTo(String propertyName,
Object literal, boolean isCaseSensitive) {
BinaryComparisonOpType propertyIsEqualTo = new BinaryComparisonOpType();
propertyIsEqualTo.setMatchCase(isCaseSensitive);
propertyIsEqualTo.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsEqualTo.getExpression().add(createLiteralType(literal));
return filterObjectFactory.createPropertyIsEqualTo(propertyIsEqualTo);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsNotEqualTo(String propertyName,
Object literal, boolean isCaseSensitive) {
BinaryComparisonOpType propertyIsNotEqualTo = new BinaryComparisonOpType();
propertyIsNotEqualTo.setMatchCase(isCaseSensitive);
propertyIsNotEqualTo.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsNotEqualTo.getExpression().add(createLiteralType(literal));
return filterObjectFactory.createPropertyIsNotEqualTo(propertyIsNotEqualTo);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsGreaterThan(String propertyName,
Object literal) {
BinaryComparisonOpType propertyIsGreaterThan = new BinaryComparisonOpType();
propertyIsGreaterThan.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsGreaterThan.getExpression().add(createLiteralType(literal));
return filterObjectFactory.createPropertyIsGreaterThan(propertyIsGreaterThan);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsGreaterThanOrEqualTo(
String propertyName, Object literal) {
BinaryComparisonOpType propertyIsGreaterThanAOrEqualTo = new BinaryComparisonOpType();
propertyIsGreaterThanAOrEqualTo.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsGreaterThanAOrEqualTo.getExpression().add(createLiteralType(literal));
return filterObjectFactory
.createPropertyIsGreaterThanOrEqualTo(propertyIsGreaterThanAOrEqualTo);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsLessThan(String propertyName,
Object literal) {
BinaryComparisonOpType propertyIsLessThan = new BinaryComparisonOpType();
propertyIsLessThan.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsLessThan.getExpression().add(createLiteralType(literal));
return filterObjectFactory.createPropertyIsLessThan(propertyIsLessThan);
}
private JAXBElement<BinaryComparisonOpType> createPropertyIsLessThanOrEqualTo(
String propertyName, Object literal) {
BinaryComparisonOpType propertyIsLessThanAOrEqualTo = new BinaryComparisonOpType();
propertyIsLessThanAOrEqualTo.getExpression()
.add(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
propertyIsLessThanAOrEqualTo.getExpression().add(createLiteralType(literal));
return filterObjectFactory.createPropertyIsLessThanOrEqualTo(propertyIsLessThanAOrEqualTo);
}
private JAXBElement<PropertyIsBetweenType> createPropertyIsBetween(String propertyName,
Object lowerBoundary, Object upperBoundary) {
PropertyIsBetweenType propertyIsBetween = new PropertyIsBetweenType();
propertyIsBetween.setLowerBoundary(createLowerBoundary(lowerBoundary));
propertyIsBetween.setUpperBoundary(createUpperBoundary(upperBoundary));
propertyIsBetween
.setExpression(createPropertyNameType(Arrays.asList(new Object[] {propertyName})));
return filterObjectFactory.createPropertyIsBetween(propertyIsBetween);
}
private JAXBElement<PropertyIsNullType> createPropertyIsNull(String propertyName) {
PropertyIsNullType propertyIsNull = new PropertyIsNullType();
propertyIsNull.setPropertyName(
createPropertyNameType(Arrays.asList(new Object[] {propertyName})).getValue());
return filterObjectFactory.createPropertyIsNull(propertyIsNull);
}
private LowerBoundaryType createLowerBoundary(Object lowerBoundary) {
LowerBoundaryType lowerBoundaryType = new LowerBoundaryType();
lowerBoundaryType.setExpression((createLiteralType(lowerBoundary)));
return lowerBoundaryType;
}
private UpperBoundaryType createUpperBoundary(Object upperBoundary) {
UpperBoundaryType upperBoundaryType = new UpperBoundaryType();
upperBoundaryType.setExpression((createLiteralType(upperBoundary)));
return upperBoundaryType;
}
private JAXBElement<LiteralType> createLiteralType(Object literalValue) {
JAXBElement<LiteralType> literalType = filterObjectFactory.createLiteral(new LiteralType());
literalType.getValue().getContent().add(literalValue.toString());
return literalType;
}
private JAXBElement<PropertyNameType> createPropertyNameType(List<Object> propertyNameValues) {
JAXBElement<PropertyNameType> propertyNameType = filterObjectFactory
.createPropertyName(new PropertyNameType());
propertyNameType.getValue().setContent(propertyNameValues);
return propertyNameType;
}
private Geometry getGeometryFromWkt(String wkt) {
try {
return new WKTReader().read(wkt);
} catch (ParseException e) {
throw new IllegalArgumentException("Unable to parse WKT: " + wkt, e);
}
}
/**
* The WKT passed into the spatial methods has the coordinates ordered in LON/LAT. This method
* will convert the WKT to LAT/LON ordering.
*/
private String convertWktToLatLonOrdering(String wktInLonLat) {
if (!isLonLatOrder) {
LOGGER.debug(
"Converting WKT from LON/LAT coordinate ordering to LAT/LON coordinate ordering.");
// Normalize all whitespace in WKT before processing.
wktInLonLat = normalizeWhitespaceInWkt(wktInLonLat);
Matcher matcher = COORD_PATTERN.matcher(wktInLonLat);
StringBuffer stringBuffer = new StringBuffer();
while (matcher.find()) {
String lonLatCoord = matcher.group();
String latLonCoord = StringUtils.reverseDelimited(lonLatCoord, ' ');
LOGGER.debug("Converted LON/LAT coord: ({}) to LAT/LON coord: ({}).", lonLatCoord,
latLonCoord);
matcher.appendReplacement(stringBuffer, latLonCoord);
}
matcher.appendTail(stringBuffer);
String wktInLatLon = stringBuffer.toString();
LOGGER.debug("Original WKT with coords in LON/LAT ordering: {}", wktInLonLat);
LOGGER.debug("Converted WKT with coords in LAT/LON ordering: {}", wktInLatLon);
return wktInLatLon;
} else {
LOGGER.debug("The configured CSW source requires coordinates in LON/LAT ordering.");
return wktInLonLat;
}
}
private String normalizeWhitespaceInWkt(String wkt) {
String normalizedWkt = wkt.replaceAll("(\\s+)?([(|)|,])(\\s+)?", "$2");
normalizedWkt = normalizedWkt.replaceAll("\\s+", " ");
return normalizedWkt;
}
@SuppressWarnings("unchecked")
private BinarySpatialOpType createBinarySpatialOpTypeUsingGeometry(
PropertyNameType propertyName, JAXBElement<? extends AbstractGeometryType> geometry) {
BinarySpatialOpType binarySpatialOpType = new BinarySpatialOpType();
binarySpatialOpType.setPropertyName(propertyName);
binarySpatialOpType.setGeometry((JAXBElement<AbstractGeometryType>) geometry);
return binarySpatialOpType;
}
private BinarySpatialOpType createBinarySpatialOpTypeUsingEnvelope(
PropertyNameType propertyName, JAXBElement<EnvelopeType> envelope) {
BinarySpatialOpType binarySpatialOpType = new BinarySpatialOpType();
binarySpatialOpType.setPropertyName(propertyName);
binarySpatialOpType.setEnvelope(envelope);
return binarySpatialOpType;
}
private BinarySpatialOpType createBinarySpatialOpType(PropertyNameType propertyName, String wkt,
BinarySpatialOperand geometryOrEnvelope) {
BinarySpatialOpType binarySpatialOpType = null;
if (geometryOrEnvelope == BinarySpatialOperand.GEOMETRY) {
wkt = convertWktToLatLonOrdering(wkt);
Geometry geometry = getGeometryFromWkt(wkt);
JAXBElement<? extends AbstractGeometryType> geometryJaxbElement = convertGeometry(
geometry);
binarySpatialOpType = createBinarySpatialOpTypeUsingGeometry(propertyName,
geometryJaxbElement);
} else {
JAXBElement<EnvelopeType> envelopeJaxbElement = createEnvelopeType(wkt);
binarySpatialOpType = createBinarySpatialOpTypeUsingEnvelope(propertyName,
envelopeJaxbElement);
}
return binarySpatialOpType;
}
}