/** * 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.endpoint.mappings; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswConstants; import org.codice.ddf.spatial.ogc.csw.catalog.common.CswRecordMetacardType; import org.codice.ddf.spatial.ogc.csw.catalog.converter.CswRecordConverter; import org.codice.ddf.spatial.ogc.csw.catalog.converter.DefaultCswRecordMap; import org.geotools.filter.FilterFactoryImpl; import org.geotools.filter.visitor.DuplicatingFilterVisitor; import org.geotools.styling.UomOgcMapping; import org.geotools.temporal.object.DefaultInstant; import org.geotools.temporal.object.DefaultPeriod; import org.geotools.temporal.object.DefaultPosition; import org.opengis.filter.And; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.Or; import org.opengis.filter.PropertyIsBetween; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.spatial.BBOX; import org.opengis.filter.spatial.Beyond; import org.opengis.filter.spatial.DWithin; import org.opengis.temporal.Instant; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.helpers.NamespaceSupport; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.Metacard; import ddf.catalog.data.impl.BasicTypes; import ddf.measure.Distance; import ddf.measure.Distance.LinearUnit; /** * CswRecordMapperFilterVisitor extends {@link DuplicatingFilterVisitor} to create a new filter * where PropertyName expressions are converted from CswRecord terminology to the framework's * Metacard terminology */ public class CswRecordMapperFilterVisitor extends DuplicatingFilterVisitor { protected static final CswRecordMetacardType CSW_METACARD_TYPE = new CswRecordMetacardType(); protected static final FilterFactory FILTER_FACTORY = new FilterFactoryImpl(); protected static final String SPATIAL_QUERY_TAG = "spatialQueryExtraData"; private static final Logger LOGGER = LoggerFactory .getLogger(CswRecordMapperFilterVisitor.class); private List<String> sourceIds = new ArrayList<>(); private Filter visitedFilter; public List<String> getSourceIds() { return sourceIds; } public Filter getVisitedFilter() { return visitedFilter; } public void setVisitedFilter(Filter filter) { visitedFilter = filter; } @Override // convert BBOX queries to Within filters. public Object visit(BBOX filter, Object extraData) { Expression geometry1 = visit(filter.getExpression1(), SPATIAL_QUERY_TAG); Expression geometry2 = visit(filter.getExpression2(), extraData); return getFactory(extraData).within(geometry1, geometry2); } @Override public Object visit(Beyond filter, Object extraData) { double distance = getDistanceInMeters(filter.getDistance(), filter.getDistanceUnits()); Expression geometry1 = visit(filter.getExpression1(), SPATIAL_QUERY_TAG); Expression geometry2 = visit(filter.getExpression2(), extraData); return getFactory(extraData) .beyond(geometry1, geometry2, distance, UomOgcMapping.METRE.name()); } @Override public Object visit(DWithin filter, Object extraData) { double distance = getDistanceInMeters(filter.getDistance(), filter.getDistanceUnits()); Expression geometry1 = visit(filter.getExpression1(), SPATIAL_QUERY_TAG); Expression geometry2 = visit(filter.getExpression2(), extraData); return getFactory(extraData) .dwithin(geometry1, geometry2, distance, UomOgcMapping.METRE.name()); } @Override public Object visit(PropertyName expression, Object extraData) { if (expression == null) { LOGGER.warn("Attempting to visit a null expression"); return null; } String propertyName = expression.getPropertyName(); String name; if (CswConstants.BBOX_PROP.equals(propertyName) || CswRecordMetacardType.OWS_BOUNDING_BOX .equals(propertyName)) { name = Metacard.ANY_GEO; } else { NamespaceSupport namespaceSupport = expression.getNamespaceContext(); name = DefaultCswRecordMap.getDefaultCswRecordMap() .getDefaultMetacardFieldForPrefixedString(propertyName, namespaceSupport); if (SPATIAL_QUERY_TAG.equals(extraData)) { AttributeDescriptor attrDesc = CSW_METACARD_TYPE.getAttributeDescriptor(name); if (attrDesc != null && !BasicTypes.GEO_TYPE.equals(attrDesc.getType())) { throw new UnsupportedOperationException( "Attempted a spatial query on a non-geometry-valued attribute (" + propertyName + ")"); } } } LOGGER.debug("Converting \"{}\" to \"{}\"", propertyName, name); return getFactory(extraData).property(name); } @Override public Object visit(PropertyIsBetween filter, Object extraData) { Expression expr = visit(filter.getExpression(), extraData); Expression lower = visit(filter.getLowerBoundary(), expr); Expression upper = visit(filter.getUpperBoundary(), expr); return getFactory(extraData).between(expr, lower, upper); } @Override public Object visit(PropertyIsEqualTo filter, Object extraData) { if (StringUtils.equals(Metacard.SOURCE_ID, ((PropertyName) filter.getExpression1()).getPropertyName())) { sourceIds.add((String) ((Literal) filter.getExpression2()).getValue()); return null; } Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).equal(expr1, expr2, matchCase); } @Override public Object visit(PropertyIsNotEqualTo filter, Object extraData) { Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); boolean matchCase = filter.isMatchingCase(); return getFactory(extraData).notEqual(expr1, expr2, matchCase); } @Override public Object visit(PropertyIsGreaterThan filter, Object extraData) { Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); // work around since Solr Provider doesn't support greater on temporal (DDF-311) if (isTemporalQuery(expr1, expr2)) { // also not supported by provider (DDF-311) //TODO: work around 1: return getFactory(extraData).after(expr1, expr2); Object val = null; Expression other = null; if (expr2 instanceof Literal) { val = ((Literal) expr2).getValue(); other = expr1; } else if (expr1 instanceof Literal) { val = ((Literal) expr1).getValue(); other = expr2; } if (val != null) { Date orig = (Date) val; orig.setTime(orig.getTime() + 1); Instant start = new DefaultInstant(new DefaultPosition(orig)); Instant end = new DefaultInstant(new DefaultPosition(new Date())); DefaultPeriod period = new DefaultPeriod(start, end); Literal literal = getFactory(extraData).literal(period); return getFactory(extraData).during(other, literal); } } return getFactory(extraData).greater(expr1, expr2); } @Override public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); // work around since Solr Provider doesn't support greaterOrEqual on temporal (DDF-311) if (isTemporalQuery(expr1, expr2)) { // also not supported by provider (DDF-311) //TEquals tEquals = getFactory(extraData).tequals(expr1, expr2); //After after = getFactory(extraData).after(expr1, expr2); //return getFactory(extraData).or(tEquals, after); Object val = null; Expression other = null; if (expr2 instanceof Literal) { val = ((Literal) expr2).getValue(); other = expr1; } else if (expr1 instanceof Literal) { val = ((Literal) expr1).getValue(); other = expr2; } if (val != null) { Date orig = (Date) val; Instant start = new DefaultInstant(new DefaultPosition(orig)); Instant end = new DefaultInstant(new DefaultPosition(new Date())); DefaultPeriod period = new DefaultPeriod(start, end); Literal literal = getFactory(extraData).literal(period); return getFactory(extraData).during(other, literal); } } return getFactory(extraData).greaterOrEqual(expr1, expr2); } @Override public Object visit(PropertyIsLessThan filter, Object extraData) { Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); // work around since solr provider doesn't support lessthan on temporal (DDF-311) if (isTemporalQuery(expr1, expr2)) { return getFactory(extraData).before(expr1, expr2); } return getFactory(extraData).less(expr1, expr2); } @Override public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { Expression expr1 = visit(filter.getExpression1(), extraData); Expression expr2 = visit(filter.getExpression2(), expr1); // work around since solr provider doesn't support lessOrEqual on temporal (DDF-311) if (isTemporalQuery(expr1, expr2)) { // work around #1 fails, solr provider doesn't support tEquals either (DDF-311) //TEquals tEquals = getFactory(extraData).tequals(expr1, expr2); //Before before = getFactory(extraData).before(expr1, expr2); //return getFactory(extraData).or(tEquals, before); Object val = null; Expression other = null; if (expr2 instanceof Literal) { val = ((Literal) expr2).getValue(); other = expr1; } else if (expr1 instanceof Literal) { val = ((Literal) expr1).getValue(); other = expr2; } if (val != null) { Date orig = (Date) val; orig.setTime(orig.getTime() + 1); Literal literal = getFactory(extraData).literal(orig); return getFactory(extraData).before(other, literal); } } return getFactory(extraData).lessOrEqual(expr1, expr2); } @Override public Object visit(Literal expression, Object extraData) { if (extraData != null && extraData instanceof PropertyName && expression .getValue() instanceof String) { String propName = ((PropertyName) extraData).getPropertyName(); AttributeDescriptor attrDesc = CSW_METACARD_TYPE.getAttributeDescriptor(propName); if (attrDesc != null && attrDesc.getType() != null) { String value = (String) expression.getValue(); Serializable convertedValue = CswRecordConverter .convertStringValueToMetacardValue(attrDesc.getType().getAttributeFormat(), value); return getFactory(extraData).literal(convertedValue); } } return getFactory(extraData).literal(expression.getValue()); } @Override public Object visit(And filter, Object extraData) { List<Filter> children = filter.getChildren(); List<Filter> newChildren = new ArrayList<Filter>(); Iterator<Filter> iter = children.iterator(); while (iter.hasNext()) { Filter child = iter.next(); if (child != null) { Filter newChild = (Filter) child.accept(this, extraData); if (newChild != null) { newChildren.add(newChild); } } } if (newChildren.isEmpty()) { return null; } if (newChildren.size() == 1) { return newChildren.get(0); } return getFactory(extraData).and(newChildren); } public Object visit(Or filter, Object extraData) { List<Filter> children = filter.getChildren(); List<Filter> newChildren = new ArrayList<Filter>(); Iterator<Filter> iter = children.iterator(); while (iter.hasNext()) { Filter child = iter.next(); if (child != null) { Filter newChild = (Filter) child.accept(this, extraData); if (newChild != null) { newChildren.add(newChild); } } } if (newChildren.isEmpty()) { return null; } if (newChildren.size() == 1) { return newChildren.get(0); } return getFactory(extraData).or(newChildren); } private boolean isTemporalQuery(Expression expr1, Expression expr2) { return isTemporalProperty(expr1) || isTemporalProperty(expr2); } private boolean isTemporalProperty(Expression expr) { if (expr instanceof PropertyName) { AttributeDescriptor attrDesc = CSW_METACARD_TYPE .getAttributeDescriptor(((PropertyName) expr).getPropertyName()); if (attrDesc != null) { return attrDesc.getType().equals(BasicTypes.DATE_TYPE); } } return false; } private double getDistanceInMeters(double distance, String units) { LinearUnit linearUnit = null; if ("meters".equals(units)) { linearUnit = LinearUnit.METER; } else if ("feet".equals(units)) { linearUnit = LinearUnit.FOOT_U_S; } else if ("statute miles".equals(units)) { linearUnit = LinearUnit.MILE; } else if ("nautical miles".equals(units)) { linearUnit = LinearUnit.NAUTICAL_MILE; } else if ("kilometers".equals(units)) { linearUnit = LinearUnit.KILOMETER; } return new Distance(distance, linearUnit).getAs(LinearUnit.METER); } protected Expression visit(Expression expression, Object extraData) { if (expression == null) { return null; } return (Expression) expression.accept(this, extraData); } }