org.codice.ddf.spatial.ogc.csw.catalog.source.CswFilterFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.spatial.ogc.csw.catalog.source.CswFilterFactory.java

Source

/**
 * 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;
    }

}